diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..c6961e7 --- /dev/null +++ b/.babelrc @@ -0,0 +1,22 @@ +{ + "presets": [ + [ + "env", + { + "targets": { + "node": "6" + } + } + ] + ], + "plugins": [ + [ + "transform-runtime", + { + "helpers": true, + "polyfill": false, + "regenerator": true + } + ] + ] +} diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..cb77ff6 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,30 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/node:6 + environment: + - NODE_ENV: "test" + branches: + only: + - master + steps: + - checkout + - run: + name: Install modules + command: make install + - run: + name: Running tests + command: make test + - run: + name: Codecov Coverage + command: ./node_modules/codecov/bin/codecov + - run: + name: Coveralls Coverage + command: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js --verbose + - run: + name: Codeclimate Coverage + command: ./node_modules/codeclimate-test-reporter/bin/codeclimate.js < ./coverage/lcov.info + - run: + name: Codacy Coverage + command: ./node_modules/.bin/codacy-coverage < ./coverage/lcov.info diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1970b3c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore index 96212a3..3881da2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,13 @@ -**/*{.,-}min.js +coverage/** +node_modules/** +api-docs +readme/ +.idea +lib/ +index.js +test/data +test/tmp +test/functional/tmp + +# TODO revert later when test is done +test/unit/test-cli.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9faa375..0000000 --- a/.eslintrc +++ /dev/null @@ -1,213 +0,0 @@ -ecmaFeatures: - modules: true - jsx: true - -env: - amd: true - browser: true - es6: true - jquery: true - node: true - -# http://eslint.org/docs/rules/ -rules: - # Possible Errors - comma-dangle: [2, never] - no-cond-assign: 2 - no-console: 0 - no-constant-condition: 2 - no-control-regex: 2 - no-debugger: 2 - no-dupe-args: 2 - no-dupe-keys: 2 - no-duplicate-case: 2 - no-empty: 2 - no-empty-character-class: 2 - no-ex-assign: 2 - no-extra-boolean-cast: 2 - no-extra-parens: 0 - no-extra-semi: 2 - no-func-assign: 2 - no-inner-declarations: [2, functions] - no-invalid-regexp: 2 - no-irregular-whitespace: 2 - no-negated-in-lhs: 2 - no-obj-calls: 2 - no-regex-spaces: 2 - no-sparse-arrays: 2 - no-unexpected-multiline: 2 - no-unreachable: 2 - use-isnan: 2 - valid-jsdoc: 0 - valid-typeof: 2 - - # Best Practices - accessor-pairs: 2 - block-scoped-var: 0 - complexity: [2, 6] - consistent-return: 0 - curly: 0 - default-case: 0 - dot-location: 0 - dot-notation: 0 - eqeqeq: 2 - guard-for-in: 2 - no-alert: 2 - no-caller: 2 - no-case-declarations: 2 - no-div-regex: 2 - no-else-return: 0 - no-empty-label: 2 - no-empty-pattern: 2 - no-eq-null: 2 - no-eval: 2 - no-extend-native: 2 - no-extra-bind: 2 - no-fallthrough: 2 - no-floating-decimal: 0 - no-implicit-coercion: 0 - no-implied-eval: 2 - no-invalid-this: 0 - no-iterator: 2 - no-labels: 0 - no-lone-blocks: 2 - no-loop-func: 2 - no-magic-number: 0 - no-multi-spaces: 0 - no-multi-str: 0 - no-native-reassign: 2 - no-new-func: 2 - no-new-wrappers: 2 - no-new: 2 - no-octal-escape: 2 - no-octal: 2 - no-proto: 2 - no-redeclare: 2 - no-return-assign: 2 - no-script-url: 2 - no-self-compare: 2 - no-sequences: 0 - no-throw-literal: 0 - no-unused-expressions: 2 - no-useless-call: 2 - no-useless-concat: 2 - no-void: 2 - no-warning-comments: 0 - no-with: 2 - radix: 2 - vars-on-top: 0 - wrap-iife: 2 - yoda: 0 - - # Strict - strict: 0 - - # Variables - init-declarations: 0 - no-catch-shadow: 2 - no-delete-var: 2 - no-label-var: 2 - no-shadow-restricted-names: 2 - no-shadow: 0 - no-undef-init: 2 - no-undef: 0 - no-undefined: 0 - no-unused-vars: 0 - no-use-before-define: 0 - - # Node.js and CommonJS - callback-return: 2 - global-require: 2 - handle-callback-err: 2 - no-mixed-requires: 0 - no-new-require: 0 - no-path-concat: 2 - no-process-exit: 2 - no-restricted-modules: 0 - no-sync: 0 - - # Stylistic Issues - array-bracket-spacing: 0 - block-spacing: 0 - brace-style: 0 - camelcase: 0 - comma-spacing: 0 - comma-style: 0 - computed-property-spacing: 0 - consistent-this: 0 - eol-last: 0 - func-names: 0 - func-style: 0 - id-length: 0 - id-match: 0 - indent: 0 - jsx-quotes: 0 - key-spacing: 0 - linebreak-style: 0 - lines-around-comment: 0 - max-depth: 0 - max-len: 0 - max-nested-callbacks: 0 - max-params: 0 - max-statements: [2, 30] - new-cap: 0 - new-parens: 0 - newline-after-var: 0 - no-array-constructor: 0 - no-bitwise: 0 - no-continue: 0 - no-inline-comments: 0 - no-lonely-if: 0 - no-mixed-spaces-and-tabs: 0 - no-multiple-empty-lines: 0 - no-negated-condition: 0 - no-nested-ternary: 0 - no-new-object: 0 - no-plusplus: 0 - no-restricted-syntax: 0 - no-spaced-func: 0 - no-ternary: 0 - no-trailing-spaces: 0 - no-underscore-dangle: 0 - no-unneeded-ternary: 0 - object-curly-spacing: 0 - one-var: 0 - operator-assignment: 0 - operator-linebreak: 0 - padded-blocks: 0 - quote-props: 0 - quotes: 0 - require-jsdoc: 0 - semi-spacing: 0 - semi: 0 - sort-vars: 0 - space-after-keywords: 0 - space-before-blocks: 0 - space-before-function-paren: 0 - space-before-keywords: 0 - space-in-parens: 0 - space-infix-ops: 0 - space-return-throw-case: 0 - space-unary-ops: 0 - spaced-comment: 0 - wrap-regex: 0 - - # ECMAScript 6 - arrow-body-style: 0 - arrow-parens: 0 - arrow-spacing: 0 - constructor-super: 0 - generator-star-spacing: 0 - no-arrow-condition: 0 - no-class-assign: 0 - no-const-assign: 0 - no-dupe-class-members: 0 - no-this-before-super: 0 - no-var: 0 - object-shorthand: 0 - prefer-arrow-callback: 0 - prefer-const: 0 - prefer-reflect: 0 - prefer-spread: 0 - prefer-template: 0 - require-yield: 0 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..3984b54 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,77 @@ +module.exports = { + extends: 'airbnb-base', + parser: 'babel-eslint', + parserOptions: { + ecmaVersion: 8, + }, + plugins: [ + 'import', + 'jsdoc', + 'json', + 'filenames', + 'jest', + 'jest-async' + ], + env: { + 'jest/globals': true + }, + rules: { + 'arrow-body-style': 'off', + 'comma-dangle': [ + 'error', { + arrays: 'only-multiline', + objects: 'only-multiline', + imports: 'only-multiline', + exports: 'only-multiline', + functions: 'ignore', + }], + 'consistent-return': 'error', + 'filenames/match-regex': ['error', '^[a-z0-9-]+$'], + 'function-paren-newline': 'off', //['error', 'multiline'], + 'import/no-amd': 'error', + 'import/no-commonjs': 'error', + 'import/prefer-default-export': 'off', + 'jest/no-disabled-tests': 'warn', + 'jest/no-focused-tests': 'error', + 'jest/no-identical-title': 'error', + 'jest/valid-expect': 'error', + 'jest-async/expect-return': 'error', + 'jsdoc/check-param-names': 'error', + 'jsdoc/check-tag-names': 'error', + 'jsdoc/check-types': 'error', + 'jsdoc/newline-after-description': 'error', + 'jsdoc/require-description-complete-sentence': 'error', + 'jsdoc/require-hyphen-before-param-description': 'error', + 'jsdoc/require-param': 'error', + 'jsdoc/require-param-description': 'error', + 'jsdoc/require-param-type': 'error', + 'jsdoc/require-returns-description': 'error', + 'jsdoc/require-returns-type': 'error', + 'max-len': [ + 'error', 120, 4, { + ignoreUrls: true, + ignoreRegExpLiterals: true, + }, + ], + 'no-case-declarations': 'error', + 'no-param-reassign': ['error', { props: false }], + 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], + 'prefer-template': 'off', + 'require-jsdoc': [ + 'error', { + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + } + }], + strict: ['error', 'never'], + }, + settings: { + jsdoc: { + additionalTagNames: { + customTags: ['resolve', 'reject'] + }, + } + } +}; diff --git a/.gitignore b/.gitignore index a93d8c0..f7bcff8 100755 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ -/bin -/docs/API.md +/readme/API.md .idea *.iml /VERSION.txt node_modules +test/functional/tmp test/tmp /.project /.settings/* @@ -11,3 +11,5 @@ npm-debug.log coverage nohup.out /jy-transform.wiki/ +/lib +/package-lock.json diff --git a/.jestrc.js b/.jestrc.js new file mode 100644 index 0000000..4485542 --- /dev/null +++ b/.jestrc.js @@ -0,0 +1,49 @@ +module.exports = { + // this is a workaround that jest does not create a jest_0/ folder in the project root dir! + cacheDirectory: '/tmp/jest-cache', + collectCoverageFrom: [ + '!lib/**/*.js', + 'src/**/*.js', + '!src/cli.js', // TODO: maybe later! + 'index.js', + 'jyt', + ], + coverageDirectory: './coverage/', + coverageReporters: ['html', 'lcov', 'lcovonly', 'text'], + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100 + } + }, + mapCoverage: true, + testMatch: [ + // '**/test/unit/test-serialize-utils.js', + // '**/test/functional/test-jyt-cli.js', + // '**/test/unit/validation/test-joi-extensions-file-helper.js', + // '**/test/unit/validation/test-joi-extensions-identifier-helper.js', + //'**/test/functional/test-transformer.js', + // '**/test/unit/test-index.js', + //'**/test/unit/test-reader.js', + // '**/test/unit/test-writer.js', + //'**/test/unit/validation/test-options-schema.js', + '**/test/unit/**/*.js', + '**/test/functional/**/*.js', +//'**/test/unit/validation/test-options-schema-helper.js', + + + //'**/test/unit/test-serialize-utils.js', + // '**/test/unit/test-middleware.js', + // '**/test/test-index.js', + //'/*.js!**/test/functional/util/**', + //'!**/test/*.js', + ], + testEnvironment: 'node', + testPathIgnorePatterns: [ + '/test/functional/tmp/.*', + '/test/data/.*', + ], + verbose: true, +}; diff --git a/.jsdoc-public.json b/.jsdoc-public.json new file mode 100644 index 0000000..1e14c9e --- /dev/null +++ b/.jsdoc-public.json @@ -0,0 +1,27 @@ +{ + "source": { + "include": [ + "./src/jy-transform.js", + "./src/type-definitions.js" + ], + "exclude": [ + "test", + "lib", + "node_modules" + ], + "includePattern": ".+\\.js$", + "excludePattern": "(^|\\/|\\\\)(_|\\.)" + }, + "opts": { + "encoding": "utf8", + "recurse": true + }, + "plugins": [ + "node_modules/jsdoc-babel" + ], + "babel": { + "plugins": [ + "transform-async-to-generator" + ] + } +} diff --git a/.jsdoc.json b/.jsdoc.json new file mode 100644 index 0000000..6835d20 --- /dev/null +++ b/.jsdoc.json @@ -0,0 +1,26 @@ +{ + "source": { + "include": [ + "src", + "test" + ], + "exclude": [ + "lib", + "node_modules" + ], + "includePattern": ".+\\.js(doc)?$", + "excludePattern": "(^|\\/|\\\\)(_|\\.)" + }, + "opts": { + "encoding": "utf8", + "recurse": true + }, + "plugins": [ + "node_modules/jsdoc-babel" + ], + "babel": { + "plugins": [ + "transform-async-to-generator" + ] + } +} diff --git a/.travis.yml b/.travis.yml index 2ceccd5..83d0cb3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,22 @@ language: node_js node_js: +- "9" - "8" - "7" - "6" -- "5" os: - linux - osx +script: +- make test + after_success: - ./node_modules/codecov/bin/codecov -e TRAVIS_NODE_VERSION - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js --verbose - ./node_modules/codeclimate-test-reporter/bin/codeclimate.js < ./coverage/lcov.info +- ./node_modules/.bin/codacy-coverage < ./coverage/lcov.info branches: only: # whitelist diff --git a/API-PRIVATE.md b/API-PRIVATE.md new file mode 100644 index 0000000..1345812 --- /dev/null +++ b/API-PRIVATE.md @@ -0,0 +1,1743 @@ + + +## TOC + +- [Modules](#modules) +- [Members](#members) +- [Constants](#constants) +- [Typedefs](#typedefs) +- [External](#external) +- [jy-transform:jyt ℗](#jy-transformjyt-%E2%84%97) +- [jy-transform:constants](#jy-transformconstants) +- [jy-transform:debug-log ℗](#jy-transformdebug-log-%E2%84%97) +- [jy-transform:io-utils ℗](#jy-transformio-utils-%E2%84%97) +- [jy-transform:reader ℗](#jy-transformreader-%E2%84%97) +- [jy-transform:serialize-utils ℗](#jy-transformserialize-utils-%E2%84%97) +- [jy-transform:transformer ℗](#jy-transformtransformer-%E2%84%97) +- [jy-transform:validation:joi-extensions-file-utils ℗](#jy-transformvalidationjoi-extensions-file-utils-%E2%84%97) +- [jy-transform:validation:joi-extensions-identifier-utils ℗](#jy-transformvalidationjoi-extensions-identifier-utils-%E2%84%97) +- [jy-transform:validation:joi-extension ℗](#jy-transformvalidationjoi-extension-%E2%84%97) +- [jy-transform:validation:options-schema-utils : Object ℗](#jy-transformvalidationoptions-schema-utils--codeobjectcode-%E2%84%97) +- [jy-transform:validation:options-schema : Object ℗](#jy-transformvalidationoptions-schema--codeobjectcode-%E2%84%97) +- [jy-transform:writer ℗](#jy-transformwriter-%E2%84%97) +- [jy-transform:unit-test:test-cli ℗](#jy-transformunit-testtest-cli-%E2%84%97) +- [jy-transform:unit-test:test-reader ℗](#jy-transformunit-testtest-reader-%E2%84%97) +- [jy-transform:unit-test:test-transformer ℗](#jy-transformunit-testtest-transformer-%E2%84%97) +- [jy-transform:unit-test:test-writer ℗](#jy-transformunit-testtest-writer-%E2%84%97) +- [jy-transform:unit:helper-constants : Object ℗](#jy-transformunithelper-constants--codeobjectcode-%E2%84%97) +- [jy-transform:unit:logger : Object ℗](#jy-transformunitlogger--codeobjectcode-%E2%84%97) +- [jy-transform:test-unit:index ℗](#jy-transformtest-unitindex-%E2%84%97) +- [jy-transform:test-unit:serialize-utils ℗](#jy-transformtest-unitserialize-utils-%E2%84%97) +- [jy-transform:test-unit:test-joi-extension-file-utils ℗](#jy-transformtest-unittest-joi-extension-file-utils-%E2%84%97) +- [jy-transform:unit-test:test-joi-extensions-identifier-utils ℗](#jy-transformunit-testtest-joi-extensions-identifier-utils-%E2%84%97) +- [jy-transform:unit-test:test-options-schema-utils ℗](#jy-transformunit-testtest-options-schema-utils-%E2%84%97) +- [jy-transform:unit-test:test-options-schema ℗](#jy-transformunit-testtest-options-schema-%E2%84%97) +- [readYamlFromfile ⇒ Object ℗](#readyamlfromfile-%E2%87%92-codeobjectcode-%E2%84%97) +- [mkdirAndWrite ℗](#mkdirandwrite-%E2%84%97) +- [readJsOrJsonFromFile ⇒ Promise.<string> ℗](#readjsorjsonfromfile-%E2%87%92-codepromiseltstringgtcode-%E2%84%97) +- [assertTransformSuccess ℗](#asserttransformsuccess-%E2%84%97) +- [assertYamlTransformSuccess](#assertyamltransformsuccess) +- [transformFunc](#transformfunc) +- [assertTransformSuccess](#asserttransformsuccess) +- [assertYamlTransformSuccess](#assertyamltransformsuccess-1) +- [_jsYaml ⇒ Object ℗](#_jsyaml-%E2%87%92-codeobjectcode-%E2%84%97) +- [fsPromisified ℗](#fspromisified-%E2%84%97) +- [fsPromisified ℗](#fspromisified-%E2%84%97-1) +- [ReadOptions : object](#readoptions--codeobjectcode) +- [WriteOptions : object](#writeoptions--codeobjectcode) +- [TransformOptions : object](#transformoptions--codeobjectcode) +- [joi ℗](#joi-%E2%84%97) + + + +## Modules + +
+
jy-transform:jyt
+

The command line interface.

+
+
jy-transform:constants
+

Useful constants used for the module and its usage.

+
+
jy-transform:debug-log
+

The debug logger. Can be enabled via environment variables (set to true):

+
    +
  • JYT_DEBUG: (only DEBUG logging via console.log)
  • +
  • JYT_DEBUG: (only ERROR logging via console.error)
  • +
+
+
jy-transform:io-utils
+

This module provides an I/O interface for files, streams or Object.

+
+
jy-transform:reader
+

This module provides the public interface for the read functionality of YAML, JS or JSON sources +(file, Object or stream.Readable).

+
+
jy-transform:serialize-utils
+

This module provides an interface for serializing JS either to string or the JSON format.

+
+
jy-transform:transformer
+

This module provides the transform functionality for YAML, JS or JSON source to destination mapping.

+
+
jy-transform:validation:joi-extensions-file-utils
+

An (extended) Joi validation schema helper functions for the module options on FS validation.

+
+
jy-transform:validation:joi-extensions-identifier-utils
+

An (extended) Joi validation schema helper function for the module options to validate ES6 identifiers.

+
+
jy-transform:validation:joi-extension
+

The module exporting the Extensions for option validations.

+
+
jy-transform:validation:options-schema-utils : Object
+

Provides some helper functions used in module:validation:options-schema to resolve default +values for origin and target depending on the options.src or options.dest value.

+
+
jy-transform:validation:options-schema : Object
+

The module options schema used in module:options-validator.

+
+
jy-transform:writer
+

This module provides the public interface for the write functionality to write JS objects from +memory to a JSON/JS/YAML destination (file, Object or stream.Writable).

+
+
jy-transform:unit-test:test-cli
+

This unit test suite checks the correct transformation behaviour of the CLI interface.

+
+
jy-transform:unit-test:test-reader
+

This unit test suite checks the validity and correctness of the Reader module.

+
+
jy-transform:unit-test:test-transformer
+

This unit test suite checks the correct transformation behaviour of the Transformer module.

+
+
jy-transform:unit-test:test-writer
+

This unit test suite checks the validity and correctness of Writer module.

+
+
jy-transform:unit:helper-constants : Object
+

The test suite constants definitions.

+
+
jy-transform:unit:logger : Object
+

The test suite logger.

+
+
jy-transform:test-unit:index
+

This unit test module tests the correct exporting from ./index.js.

+
+
jy-transform:test-unit:serialize-utils
+

This unit test suite checks the validity and correctness of JS serialization utility methods.

+
+
jy-transform:test-unit:test-joi-extension-file-utils
+

This unit test module tests validation FS helper method.

+
+
jy-transform:unit-test:test-joi-extensions-identifier-utils
+

This unit test suite checks validity and correctness of ES6 identifiers.

+
+
jy-transform:unit-test:test-options-schema-utils
+

This unit test suite checks the validity and correctness of options schema helper methods.

+
+
jy-transform:unit-test:test-options-schema
+

This unit test suite checks the validity and correctness of options schema.

+
+
+ +## Members + +
+
readYamlFromfileObject
+

Reads YAML from file.

+
+
mkdirAndWrite
+

Ensures that all dirs exists for file type dest and writes the JS object to file.

+
+
readJsOrJsonFromFilePromise.<string>
+

Writes a serialized object to file.

+
+
assertTransformSuccess
+

Helper method which asserts the successful transformation.

+
+
assertYamlTransformSuccess
+

Helper method which asserts the successful transformation.

+
+
transformFunc
+

Transformation middleware changing value for foo property.

+
+
assertTransformSuccess
+

Helper method which asserts the successful transformation.

+
+
assertYamlTransformSuccess
+

Helper method which asserts the successful transformation.

+
+
_jsYamlObject
+

Creates the options from the given transform function, source and destination path parameters.

+
+
+ +## Constants + +
+
fsPromisified
+

Promisified fs module.

+
+
fsPromisified
+

Promisified fs module.

+
+
+ +## Typedefs + +
+
ReadOptions : object
+

The configuration properties provided to the read function.

+
+
WriteOptions : object
+

The configuration properties provided to the write function.

+
+
TransformOptions : object
+

The configuration properties provided to the transform function.

+
+
+ +## External + +
+
joi
+

Hapi.js Joi.

+
+
+ + + +## jy-transform:jyt ℗ +The command line interface. + +**Access**: private + +* [jy-transform:jyt](#module_jy-transform_jyt) ℗ + * [~usage](#module_jy-transform_jyt..usage) : string ℗ + * [~packagePath](#module_jy-transform_jyt..packagePath) : string ℗ + * [~cliOptionsSchema](#module_jy-transform_jyt..cliOptionsSchema) : Object ℗ + * [~error(err)](#module_jy-transform_jyt..error) ℗ + * [~main(args, cliOptions)](#module_jy-transform_jyt..main) ℗ + + + +### jy-transform:jyt~usage : string ℗ +How to use the CLI. + +**Kind**: inner constant of [jy-transform:jyt](#module_jy-transform_jyt) +**Access**: private + + +### jy-transform:jyt~packagePath : string ℗ +The path to package.json. + +**Kind**: inner constant of [jy-transform:jyt](#module_jy-transform_jyt) +**Access**: private + + +### jy-transform:jyt~cliOptionsSchema : Object ℗ +The options description for parsing the command line input, must be an object with opts defined like: +``` +long_tag: [short_tag, description, value_type, default_value]; +``` + +**Kind**: inner constant of [jy-transform:jyt](#module_jy-transform_jyt) +**Access**: private + + +### jy-transform:jyt~error(err) ℗ +Prints the error to console and exits process with 1. + +**Kind**: inner method of [jy-transform:jyt](#module_jy-transform_jyt) +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| err | string \| Error | The error to print. | + + + +### jy-transform:jyt~main(args, cliOptions) ℗ +The main entry callback. When calling `cli.main()` this receives the `options` +given on CLI, then does the transformation with these options and finally, it +prints the result to the CLI. + +**Kind**: inner method of [jy-transform:jyt](#module_jy-transform_jyt) +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| args | Array | The first mandatory argument is the input file (`args[0]`), the second (optional) argument is the output file (`args[1]`). | +| cliOptions | module:jy-transform:type-definitions~TransformOptions | The options provided via CLI. | + + + +## jy-transform:constants +Useful constants used for the module and its usage. + +**Access**: public + +* [jy-transform:constants](#module_jy-transform_constants) + * [~UTF8](#module_jy-transform_constants..UTF8) : string ℗ + * [~TYPE_YAML](#module_jy-transform_constants..TYPE_YAML) : string + * [~TYPE_JSON](#module_jy-transform_constants..TYPE_JSON) : string + * [~TYPE_JS](#module_jy-transform_constants..TYPE_JS) : string + * [~EXT_TO_TYPE_MAP](#module_jy-transform_constants..EXT_TO_TYPE_MAP) : Object ℗ + * [~DEFAULT_INDENT](#module_jy-transform_constants..DEFAULT_INDENT) : number ℗ + * [~MIN_INDENT](#module_jy-transform_constants..MIN_INDENT) : number ℗ + * [~MIN_YAML_INDENT](#module_jy-transform_constants..MIN_YAML_INDENT) : number ℗ + * [~MAX_INDENT](#module_jy-transform_constants..MAX_INDENT) : number ℗ + * [~DEFAULT_ORIGIN](#module_jy-transform_constants..DEFAULT_ORIGIN) : string ℗ + * [~DEFAULT_TARGET](#module_jy-transform_constants..DEFAULT_TARGET) : string ℗ + * [~DEFAULT_FORCE_FILE_OVERWRITE](#module_jy-transform_constants..DEFAULT_FORCE_FILE_OVERWRITE) : boolean ℗ + * [~DEFAULT_STRICT](#module_jy-transform_constants..DEFAULT_STRICT) : boolean ℗ + * [~DEFAULT_NO_ES6](#module_jy-transform_constants..DEFAULT_NO_ES6) : boolean ℗ + * [~DEFAULT_NO_SINGLE_QUOTES](#module_jy-transform_constants..DEFAULT_NO_SINGLE_QUOTES) : boolean ℗ + * [~ORIGIN_DESCRIPTION](#module_jy-transform_constants..ORIGIN_DESCRIPTION) : string ℗ + * [~TARGET_DESCRIPTION](#module_jy-transform_constants..TARGET_DESCRIPTION) : string ℗ + * [~DEST_DESCRIPTION](#module_jy-transform_constants..DEST_DESCRIPTION) : string ℗ + * [~DEFAULT_JS_IMPORTS_IDENTIFIER](#module_jy-transform_constants..DEFAULT_JS_IMPORTS_IDENTIFIER) : string ℗ + * [~DEFAULT_JS_EXPORTS_IDENTIFIER](#module_jy-transform_constants..DEFAULT_JS_EXPORTS_IDENTIFIER) : string ℗ + + + +### jy-transform:constants~UTF8 : string ℗ +The 'utf8' constant. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~TYPE_YAML : string +The `'yaml'` type constant. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: public + + +### jy-transform:constants~TYPE_JSON : string +The `'json'` type constant. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: public + + +### jy-transform:constants~TYPE_JS : string +The `'js'` type constant. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: public + + +### jy-transform:constants~EXT_TO_TYPE_MAP : Object ℗ +A map for extensions to type. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_INDENT : number ℗ +The default file indention (4 SPACEs). + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~MIN_INDENT : number ℗ +The minimum file indention (0 SPACE) fo JS and JSON types. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~MIN_YAML_INDENT : number ℗ +The minimum file indention (0 SPACE) for YAML types. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~MAX_INDENT : number ℗ +The maximum file indention (8 SPACEs). + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_ORIGIN : string ℗ +The default `origin` value: 'yaml'. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_TARGET : string ℗ +The default `origin` value: 'js'. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_FORCE_FILE_OVERWRITE : boolean ℗ +Whether to overwrite existing file or object on output. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_STRICT : boolean ℗ +Whether to write a "use strict;" in JS type output. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_NO_ES6 : boolean ℗ +Whether _not_ to use ECMAScript6 syntax for JS type output. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_NO_SINGLE_QUOTES : boolean ℗ +Whether _not_ to use single-quotes style for values in JS type output (i.e. double-quotes). + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~ORIGIN_DESCRIPTION : string ℗ +The `origin` description value. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~TARGET_DESCRIPTION : string ℗ +The `target` description value. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEST_DESCRIPTION : string ℗ +The `dest` description value. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +### jy-transform:constants~DEFAULT_JS_IMPORTS_IDENTIFIER : string ℗ +The `src` exports identifier value to read. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private +**Example** +```js +module.exports.foo = {...}; // here 'foo' is the identifier for an object to read from the source! +``` + + +### jy-transform:constants~DEFAULT_JS_EXPORTS_IDENTIFIER : string ℗ +The `dest` exports identifier value to write. + +**Kind**: inner constant of [jy-transform:constants](#module_jy-transform_constants) +**Access**: private + + +## jy-transform:debug-log ℗ +The debug logger. Can be enabled via environment variables (set to `true`): +- `JYT_DEBUG`: (only DEBUG logging via `console.log`) +- `JYT_DEBUG`: (only ERROR logging via `console.error`) + +**Access**: private + +* [jy-transform:debug-log](#module_jy-transform_debug-log) ℗ + * [~debug](#module_jy-transform_debug-log..debug) + * [~error](#module_jy-transform_debug-log..error) + + + +### jy-transform:debug-log~debug +DEBUG function. + +**Kind**: inner constant of [jy-transform:debug-log](#module_jy-transform_debug-log) +**Access**: protected + + +### jy-transform:debug-log~error +DEBUG function. + +**Kind**: inner constant of [jy-transform:debug-log](#module_jy-transform_debug-log) +**Access**: protected + + +## jy-transform:io-utils ℗ +This module provides an I/O interface for files, streams or `Object`. + +**Access**: private + +* [jy-transform:io-utils](#module_jy-transform_io-utils) ℗ + * [~readJsOrJsonFromFile(file, [imports])](#module_jy-transform_io-utils..readJsOrJsonFromFile) ⇒ Object ℗ + * [~readJsFromObject(object, [imports])](#module_jy-transform_io-utils..readJsFromObject) ⇒ Object ℗ + * [~getConsecutiveDestName(dest)](#module_jy-transform_io-utils..getConsecutiveDestName) ⇒ string ℗ + * [~writeToStream(object, dest, target)](#module_jy-transform_io-utils..writeToStream) ⇒ Promise.<string> ℗ + + + +### jy-transform:io-utils~readJsOrJsonFromFile(file, [imports]) ⇒ Object ℗ +Reads JS or JSON from file. + +**Kind**: inner method of [jy-transform:io-utils](#module_jy-transform_io-utils) +**Returns**: Object - The read object. +**Throws**: + +- Error When an `imports` is given but the declared object key is not exported by the file. + +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| file | string | The file path. | +| [imports] | string | An object which is exported in the file. | + + + +### jy-transform:io-utils~readJsFromObject(object, [imports]) ⇒ Object ℗ +Reads JS from JS object. + +**Kind**: inner method of [jy-transform:io-utils](#module_jy-transform_io-utils) +**Returns**: Object - The given `object` object or any sub-object specified by `imports`. +**Throws**: + +- Error When an `imports` is given but the declared object key is not contained in the source object. + +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| object | string | The JS object source. | +| [imports] | string | An object which is is a sub-object in `object`. | + + + +### jy-transform:io-utils~getConsecutiveDestName(dest) ⇒ string ℗ +Turns the destination file name into a name containing a consecutive +number if it exists. It iterates over the files until it finds a file +name which does not exist. + +**Kind**: inner method of [jy-transform:io-utils](#module_jy-transform_io-utils) +**Returns**: string - A consecutive file name or the original one if `dest` file does not exist. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| dest | string | The destination file. | + + + +### jy-transform:io-utils~writeToStream(object, dest, target) ⇒ Promise.<string> ℗ +Writes a string serialized data object to a stream. + +**Kind**: inner method of [jy-transform:io-utils](#module_jy-transform_io-utils) +**Returns**: Promise.<string> - Containing the write success message to handle by caller (e.g. for logging). +**Throws**: + +- Error If serialized JS object could not be written due to any reason. + +**Access**: private +**See** + +- [TYPE_YAML](TYPE_YAML) +- [TYPE_JSON](TYPE_JSON) +- [TYPE_JS](TYPE_JS) + + +| Param | Type | Description | +| --- | --- | --- | +| object | string | The data to write into stream. | +| dest | string | The stream destination. | +| target | string | The target type, one of [ 'yaml' | 'json' | 'js' ]. | + + + +## jy-transform:reader ℗ +This module provides the _public_ interface for the _read_ functionality of YAML, JS or JSON sources +(file, `Object` or [stream.Readable](stream.Readable)). + +**Access**: private + +* [jy-transform:reader](#module_jy-transform_reader) ℗ + * [~readJsOrJson](#module_jy-transform_reader..readJsOrJson) ⇒ Promise.<Object> ℗ + * [~readYaml](#module_jy-transform_reader..readYaml) ⇒ Promise.<Object> ℗ + * [~read](#module_jy-transform_reader..read) ⇒ Promise ℗ + + + +### jy-transform:reader~readJsOrJson ⇒ Promise.<Object> ℗ +Reads the data from a given JS or JSON source. + +**Kind**: inner property of [jy-transform:reader](#module_jy-transform_reader) +**Returns**: Promise.<Object> - Contains the read JS object. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| options | [ReadOptions](#ReadOptions) | Contains the JS/JSON source reference to read from. | + + + +### jy-transform:reader~readYaml ⇒ Promise.<Object> ℗ +Loads a single YAML source containing document and returns a JS object. +*NOTE:* this function does not understand multi-document sources, it throws +exception on those. + +**Kind**: inner property of [jy-transform:reader](#module_jy-transform_reader) +**Returns**: Promise.<Object> - Contains the read JS object. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| options | [ReadOptions](#ReadOptions) | Contains the YAML source reference to read from. | + + + +### jy-transform:reader~read ⇒ Promise ℗ +Reads a particular content type from a source provided in the passed `options`. + +**Kind**: inner property of [jy-transform:reader](#module_jy-transform_reader) +**Returns**: Promise - The result. +**Access**: private +**Resolve**: string Resolves with JS object result. +**Reject**: ValidationError If any `options` validation occurs. +**Reject**: Error If any write error occurs. + +| Param | Type | Description | +| --- | --- | --- | +| options | [ReadOptions](#ReadOptions) | The read options. | + +**Example** +```js +import { read } from 'jy-transform'; + + +// --- from file path + +options = { + src: 'foo.yml' +}; + +read(options) + .then(obj => console.log(JSON.stringify(obj))) + .catch(console.error); + + +// --- from Readable + +options = { + src: fs.createReadStream('foo.yml') +}; + +read(options) + .then(obj => console.log(JSON.stringify(obj))) + .catch(console.error); +``` + + +## jy-transform:serialize-utils ℗ +This module provides an interface for serializing JS either to string or the JSON format. + +**Access**: private + +* [jy-transform:serialize-utils](#module_jy-transform_serialize-utils) ℗ + * [~createExportString](#module_jy-transform_serialize-utils..createExportString) ⇒ Promise.<string> ℗ + * [~serializeJsToString](#module_jy-transform_serialize-utils..serializeJsToString) ⇒ Promise.<string> ℗ + * [~serializeJsToJsonString](#module_jy-transform_serialize-utils..serializeJsToJsonString) ⇒ string ℗ + + + +### jy-transform:serialize-utils~createExportString ⇒ Promise.<string> ℗ +Creates a potential named `'module.exports[.exportsTo]'` string. + +**Kind**: inner property of [jy-transform:serialize-utils](#module_jy-transform_serialize-utils) +**Returns**: Promise.<string> - Resolves with the exports string. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| es6 | boolean | Whether to use ECMAScript6 export syntax. | +| [exportsTo] | string | The export name. | + + + +### jy-transform:serialize-utils~serializeJsToString ⇒ Promise.<string> ℗ +Serialize a JS object to string. + +**Kind**: inner property of [jy-transform:serialize-utils](#module_jy-transform_serialize-utils) +**Returns**: Promise.<string> - - Promise resolve with the serialized JS content. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | The JS Object to serialize. | +| options | [WriteOptions](#WriteOptions) | The write options. | + + + +### jy-transform:serialize-utils~serializeJsToJsonString ⇒ string ℗ +Serialize a JS object to JSON string. + +**Kind**: inner property of [jy-transform:serialize-utils](#module_jy-transform_serialize-utils) +**Returns**: string - The serialized JSON. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | The object to serialize. | +| indent | number | The code indention. | + + + +## jy-transform:transformer ℗ +This module provides the _transform_ functionality for YAML, JS or JSON source to destination mapping. + +**Access**: private + + +### jy-transform:transformer~transform ⇒ Promise +The entry method for all transformations accepting a configuration object and +an (optional) middleware function. It executes the transformation logic. + +1. Input (read) +2. Transform [ + Middleware] +3. Output (write). + +**Kind**: inner property of [jy-transform:transformer](#module_jy-transform_transformer) +**Returns**: Promise - The transformation result. +**Access**: public +**Resolve**: string With the transformation result as message (e.g. to be logged by caller). +**Reject**: TypeError Will throw this error when the passed `middleware` is not type of `Function`. +**Reject**: ValidationError If any `options` validation occurs. +**Reject**: Error Will throw any error if read, transform or write operation failed due to any reason. + +| Param | Type | Description | +| --- | --- | --- | +| options | [TransformOptions](#TransformOptions) | The configuration for a transformation. | + +**Example** +```js +import { transform } from 'jy-transform'; +const options = { + src: 'foo/bar.yaml', // From YAML file... + transform: async (object) => { // ...with exchanging value... + object.foo = 'new value'; + return object; + }, + target: 'foo/bar.json', // ...to a new JSON file. + indent: 4, +}; + +// ---- Promise style: + +transform(options) + .then(console.log) + .catch(console.error); + + +// ---- async/await style: + +try { + const msg = await transform(options); + console.log(msg); +} catch (err) { + console.error(err.stack); +}; +``` + + +## jy-transform:validation:joi-extensions-file-utils ℗ +An (extended) Joi validation schema helper functions for the module options on FS validation. + +**Access**: private + + +### jy-transform:validation:joi-extensions-file-utils~isExistingFile(pathStr) ⇒ boolean +Checks if given `pathStr` is an existing file after resolving `pathStr` relative to CWD. + +**Kind**: inner method of [jy-transform:validation:joi-extensions-file-utils](#module_jy-transform_validation_joi-extensions-file-utils) +**Returns**: boolean - Value `true` if it is a file and exists, else `false`. +**Access**: protected + +| Param | Type | Description | +| --- | --- | --- | +| pathStr | string | The string to check for being a file. | + + + +## jy-transform:validation:joi-extensions-identifier-utils ℗ +An (extended) Joi validation schema helper function for the module options to validate ES6 identifiers. + +**Access**: private + +* [jy-transform:validation:joi-extensions-identifier-utils](#module_jy-transform_validation_joi-extensions-identifier-utils) ℗ + * [~identifierRegExpECMAScript6](#module_jy-transform_validation_joi-extensions-identifier-utils..identifierRegExpECMAScript6) : RegExp ℗ + * [~isValidEs6Identifier(identifier)](#module_jy-transform_validation_joi-extensions-identifier-utils..isValidEs6Identifier) ⇒ boolean + + + +### jy-transform:validation:joi-extensions-identifier-utils~identifierRegExpECMAScript6 : RegExp ℗ +Created at [Generating a regular expression to match valid JavaScript identifiers] +(https://mathiasbynens.be/demo/javascript-identifier-regex). + +**Kind**: inner constant of [jy-transform:validation:joi-extensions-identifier-utils](#module_jy-transform_validation_joi-extensions-identifier-utils) +**Access**: private + + +### jy-transform:validation:joi-extensions-identifier-utils~isValidEs6Identifier(identifier) ⇒ boolean +This method checks if a given `identifier` is a valid ECMAScript 6 identifier. + +**Kind**: inner method of [jy-transform:validation:joi-extensions-identifier-utils](#module_jy-transform_validation_joi-extensions-identifier-utils) +**Returns**: boolean - A `true` if valid, else `false`. +**Access**: protected + +| Param | Type | Description | +| --- | --- | --- | +| identifier | string | The identifier to check. | + +**Example** +```js +import { isValidEs6Identifier } from './validation/joi-extensions-identifier-utils.js'; +const identifier = 'foo'; +console.log('valid = ' + isValidEs6Identifier(identifier)); +``` + + +## jy-transform:validation:joi-extension ℗ +The module exporting the [Extension](#external_joi.Extension)s for option validations. + +**Access**: private +**See**: [Joi.extend(extension) method](https://github.com/hapijs/joi/blob/v10.2.2/API.md#extendextension). + + +### jy-transform:validation:joi-extension~EXTENSIONS : [Extension](#external_joi.Extension) ℗ +The common [Schema](#external_joi.Schema) validation extensions: +- `existingFile` - needs to be an absolute or relative path to an existing file. +- `validEs6Identifier` - needs to be a valid ECMAScript 6 identifier. + +**Kind**: inner constant of [jy-transform:validation:joi-extension](#module_jy-transform_validation_joi-extension) +**Access**: private + + +## jy-transform:validation:options-schema-utils : Object ℗ +Provides some helper functions used in [module:validation:options-schema](module:validation:options-schema) to resolve default +values for origin and target depending on the `options.src` or `options.dest` value. + +**Access**: private +**See**: [module:validation:options-schema](module:validation:options-schema) + +* [jy-transform:validation:options-schema-utils](#module_jy-transform_validation_options-schema-utils) : Object ℗ + * [~inferDestDefaultFromSrc](#module_jy-transform_validation_options-schema-utils..inferDestDefaultFromSrc) ⇒ string \| undefined + * [~inferOriginDefault](#module_jy-transform_validation_options-schema-utils..inferOriginDefault) ⇒ string + * [~inferTargetDefault](#module_jy-transform_validation_options-schema-utils..inferTargetDefault) ⇒ string + * [~isFileStream(object)](#module_jy-transform_validation_options-schema-utils..isFileStream) ⇒ boolean ℗ + * [~adaptTargetPathType(dest, [target])](#module_jy-transform_validation_options-schema-utils..adaptTargetPathType) ⇒ string ℗ + * [~getTypeFromFilePath(pathStr, defaultValue)](#module_jy-transform_validation_options-schema-utils..getTypeFromFilePath) ⇒ string ℗ + + + +### jy-transform:validation:options-schema-utils~inferDestDefaultFromSrc ⇒ string \| undefined +This function is used to infer a _default_ value in case `options.dest` is not defined. +Checks if `context.src` is either a string or a file stream where can get the file path from. +If this detection process cannot be fulfilled (i.e. we cannot infer from options.src `Object` +type or a `Readable` type which is not a _file_ stream) then the function returns `undefined`. + +**Kind**: inner constant of [jy-transform:validation:options-schema-utils](#module_jy-transform_validation_options-schema-utils) +**Returns**: string \| undefined - The adapted `dest` path if possible, or `undefined`. + +| Param | Type | Description | +| --- | --- | --- | +| context | Object | The validation context. | + + + +### jy-transform:validation:options-schema-utils~inferOriginDefault ⇒ string +Infers the _origin_ type value from current validation context. + +**Kind**: inner constant of [jy-transform:validation:options-schema-utils](#module_jy-transform_validation_options-schema-utils) +**Returns**: string - The target type. +**Access**: protected + +| Param | Type | Description | +| --- | --- | --- | +| context | Object | The validation context. | + + + +### jy-transform:validation:options-schema-utils~inferTargetDefault ⇒ string +Infers the _target_ type value from current validation context. + +**Kind**: inner constant of [jy-transform:validation:options-schema-utils](#module_jy-transform_validation_options-schema-utils) +**Returns**: string - The target type. +**Access**: protected + +| Param | Type | Description | +| --- | --- | --- | +| context | Object | The validation context. | + + + +### jy-transform:validation:options-schema-utils~isFileStream(object) ⇒ boolean ℗ +Checks if passed `object` is a file stream instance. + +**Kind**: inner method of [jy-transform:validation:options-schema-utils](#module_jy-transform_validation_options-schema-utils) +**Returns**: boolean - A `true` if passed `object` is a file stream instance, else `false`. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| object | \* | The object to check. | + + + +### jy-transform:validation:options-schema-utils~adaptTargetPathType(dest, [target]) ⇒ string ℗ +Returns the passes `dest` value or an adapted destination path (the latter if `target` is defined an differs from +destinations path extension). + +**Kind**: inner method of [jy-transform:validation:options-schema-utils](#module_jy-transform_validation_options-schema-utils) +**Returns**: string - The `dest` value or an adapted destination path. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| dest | string | The destination path. | +| [target] | string | The target file type of destination. | + + + +### jy-transform:validation:options-schema-utils~getTypeFromFilePath(pathStr, defaultValue) ⇒ string ℗ +Infer from path extension to a type using default value as fallback. + +**Kind**: inner method of [jy-transform:validation:options-schema-utils](#module_jy-transform_validation_options-schema-utils) +**Returns**: string - A type value. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| pathStr | string | The file path with or without extension. | +| defaultValue | string | The default value to use if type cannot be inferred from path. | + + + +## jy-transform:validation:options-schema : Object ℗ +The module options schema used in [module:options-validator](module:options-validator). + +**Access**: private +**See**: [module:options-validator](module:options-validator) + +* [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) : Object ℗ + * [~forceSchema](#module_jy-transform_validation_options-schema..forceSchema) : [Schema](#external_joi.Schema) ℗ + * [~strictSchema](#module_jy-transform_validation_options-schema..strictSchema) : [Schema](#external_joi.Schema) ℗ + * [~noES6Schema](#module_jy-transform_validation_options-schema..noES6Schema) : [Schema](#external_joi.Schema) ℗ + * [~noSingleSchema](#module_jy-transform_validation_options-schema..noSingleSchema) : [Schema](#external_joi.Schema) ℗ + * [~indentSchema](#module_jy-transform_validation_options-schema..indentSchema) : [Schema](#external_joi.Schema) ℗ + * [~exportsSchema](#module_jy-transform_validation_options-schema..exportsSchema) : [Schema](#external_joi.Schema) ℗ + * [~targetSchema](#module_jy-transform_validation_options-schema..targetSchema) : [Schema](#external_joi.Schema) ℗ + * [~readOptionsSchema](#module_jy-transform_validation_options-schema..readOptionsSchema) : [Schema](#external_joi.Schema) ℗ + * [~writeOptionsSchema](#module_jy-transform_validation_options-schema..writeOptionsSchema) : [Schema](#external_joi.Schema) ℗ + * [~transformOptionsSchema](#module_jy-transform_validation_options-schema..transformOptionsSchema) : [Schema](#external_joi.Schema) ℗ + + + +### jy-transform:validation:options-schema~forceSchema : [Schema](#external_joi.Schema) ℗ +The `force` option schema. + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~strictSchema : [Schema](#external_joi.Schema) ℗ +The `force` option schema. + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~noES6Schema : [Schema](#external_joi.Schema) ℗ +The `no-es6` option schema. + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~noSingleSchema : [Schema](#external_joi.Schema) ℗ +The `no-single` option schema. + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~indentSchema : [Schema](#external_joi.Schema) ℗ +The `indent` option schema. + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~exportsSchema : [Schema](#external_joi.Schema) ℗ +The `exports` option schema. + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~targetSchema : [Schema](#external_joi.Schema) ℗ +The `target` option schema. + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~readOptionsSchema : [Schema](#external_joi.Schema) ℗ +The prepared [Schema](#external_joi.Schema) for validating the [ReadOptions](#ReadOptions). + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~writeOptionsSchema : [Schema](#external_joi.Schema) ℗ +The prepared [Schema](#external_joi.Schema) for validating the [WriteOptions](#WriteOptions). + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +### jy-transform:validation:options-schema~transformOptionsSchema : [Schema](#external_joi.Schema) ℗ +The prepared [Schema](#external_joi.Schema) for validating the [TransformOptions](#TransformOptions). + +**Kind**: inner constant of [jy-transform:validation:options-schema](#module_jy-transform_validation_options-schema) +**Access**: private + + +## jy-transform:writer ℗ +This module provides the _public_ interface for the _write_ functionality to write JS objects from +memory to a JSON/JS/YAML destination (file, `Object` or [stream.Writable](stream.Writable)). + +**Access**: private + +* [jy-transform:writer](#module_jy-transform_writer) ℗ + * [~writeYaml](#module_jy-transform_writer..writeYaml) ⇒ Promise.<string> ℗ + * [~writeJson](#module_jy-transform_writer..writeJson) ⇒ Promise.<string> ℗ + * [~writeJs](#module_jy-transform_writer..writeJs) ⇒ Promise.<string> ℗ + * [~write](#module_jy-transform_writer..write) ⇒ Promise + + + +### jy-transform:writer~writeYaml ⇒ Promise.<string> ℗ +Writes a JS object to a YAML destination. + +**Kind**: inner property of [jy-transform:writer](#module_jy-transform_writer) +**Returns**: Promise.<string> - Containing the write success message to handle by caller (e.g. for logging). +**Throws**: + +- Error If YAML destination could not be written due to any reason. + +**Access**: private +**See** + +- [MIN_INDENT](MIN_INDENT) +- [DEFAULT_INDENT](DEFAULT_INDENT) +- [MAX_INDENT](MAX_INDENT) + + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | The JS object to write into YAML destination. | +| options | [WriteOptions](#WriteOptions) | The write destination and indention. | + + + +### jy-transform:writer~writeJson ⇒ Promise.<string> ℗ +Writes a JS object to a JSON destination. + +**Kind**: inner property of [jy-transform:writer](#module_jy-transform_writer) +**Returns**: Promise.<string> - Containing the write success message to handle by caller (e.g. for logging). +**Access**: private +**See** + +- [MIN_INDENT](MIN_INDENT) +- [DEFAULT_INDENT](DEFAULT_INDENT) +- [MAX_INDENT](MAX_INDENT) + + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | The JS object to write into JSON destination. | +| options | [WriteOptions](#WriteOptions) | The write destination and indention. | + + + +### jy-transform:writer~writeJs ⇒ Promise.<string> ℗ +Writes a JS object to a JS destination. + +**Kind**: inner property of [jy-transform:writer](#module_jy-transform_writer) +**Returns**: Promise.<string> - Containing the write success message to handle by caller (e.g. for logging). +**Access**: private +**See** + +- [MIN_INDENT](MIN_INDENT) +- [DEFAULT_INDENT](DEFAULT_INDENT) +- [MAX_INDENT](MAX_INDENT) + + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | The JSON to write into JS destination. | +| options | [WriteOptions](#WriteOptions) | The write options. | + + + +### jy-transform:writer~write ⇒ Promise +Writes the passed JS object to a particular destination described by the passed `options`. + +**Kind**: inner property of [jy-transform:writer](#module_jy-transform_writer) +**Returns**: Promise - The result. +**Access**: public +**Resolve**: string With the write success message. +**Reject**: Error If any write error occurs. +**Reject**: ValidationError If any `options` validation occurs. + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | The JS source object to write. | +| options | [WriteOptions](#WriteOptions) | The write options. | + +**Example** +```js +import { write } from 'jy-transform'; + + +// ---- write obj to file --- + +const obj = {...}; +const options = { + dest: 'result.js', + indent: 4 +} + +write(obj, options) + .then(console.log) + .catch(console.error); + + +// ---- write obj to Writable --- + +options = { + dest: fs.createWriteStream('result.json'), + indent: 4 +} + +write(obj, options) + .then(console.log) + .catch(console.error); + + +// ---- write obj to object --- + +options = { + dest: {}, + indent: 4 +} + +write(obj, options) + .then(console.log) + .catch(console.error); +``` + + +## jy-transform:unit-test:test-cli ℗ +This unit test suite checks the correct transformation behaviour of the CLI interface. + +**Access**: private + +* [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) ℗ + * [~CLI_TEST_BASE_DIR](#module_jy-transform_unit-test_test-cli..CLI_TEST_BASE_DIR) : string ℗ + * [~SRC_YAML](#module_jy-transform_unit-test_test-cli..SRC_YAML) : string ℗ + * [~SRC_JSON](#module_jy-transform_unit-test_test-cli..SRC_JSON) : string ℗ + * [~SRC_JS](#module_jy-transform_unit-test_test-cli..SRC_JS) : string ℗ + * [~CLI_OPTIONS_LONG_TO_SHORT_MAP](#module_jy-transform_unit-test_test-cli..CLI_OPTIONS_LONG_TO_SHORT_MAP) : Object ℗ + * [~createOptions(src, dest)](#module_jy-transform_unit-test_test-cli..createOptions) ⇒ Object ℗ + * [~execJyt(args)](#module_jy-transform_unit-test_test-cli..execJyt) ⇒ Promise ℗ + * [~optionsToArgs(options)](#module_jy-transform_unit-test_test-cli..optionsToArgs) ⇒ Array.<string> ℗ + + + +### jy-transform:unit-test:test-cli~CLI_TEST_BASE_DIR : string ℗ +Temporary base dir for transformer test output. + +**Kind**: inner constant of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Access**: private + + +### jy-transform:unit-test:test-cli~SRC_YAML : string ℗ +A YAML source file path. + +**Kind**: inner constant of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Access**: private + + +### jy-transform:unit-test:test-cli~SRC_JSON : string ℗ +A JSON source file path. + +**Kind**: inner constant of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Access**: private + + +### jy-transform:unit-test:test-cli~SRC_JS : string ℗ +A JS source file path. + +**Kind**: inner constant of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Access**: private + + +### jy-transform:unit-test:test-cli~CLI_OPTIONS_LONG_TO_SHORT_MAP : Object ℗ +Object mapping from JS option to to short CLI option. + +**Kind**: inner constant of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Access**: private + + +### jy-transform:unit-test:test-cli~createOptions(src, dest) ⇒ Object ℗ +Creates the options from the given source and destination path parameters. + +**Kind**: inner method of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Returns**: Object - The options object. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| src | string | The source path. | +| dest | string | The destination path. | + + + +### jy-transform:unit-test:test-cli~execJyt(args) ⇒ Promise ℗ +Executes `./jyt` script with given args (which includes source, destination and all options). + +**Kind**: inner method of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Returns**: Promise - A result, see details. +**Access**: private +**Resolve**: string The transformation success message. +**Reject**: Error Any error occurred. + +| Param | Type | Description | +| --- | --- | --- | +| args | Array.<string> | The source, destination CLI arguments and all CLI options. | + + + +### jy-transform:unit-test:test-cli~optionsToArgs(options) ⇒ Array.<string> ℗ +Creates the CLI args/options from given `options` object. + +**Kind**: inner method of [jy-transform:unit-test:test-cli](#module_jy-transform_unit-test_test-cli) +**Returns**: Array.<string> - The CLI args and options. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| options | [TransformOptions](#TransformOptions) | The transformation options. | + + + +## jy-transform:unit-test:test-reader ℗ +This unit test suite checks the validity and correctness of the Reader module. + +**Access**: private + + +## jy-transform:unit-test:test-transformer ℗ +This unit test suite checks the correct transformation behaviour of the Transformer module. + +**Access**: private + +* [jy-transform:unit-test:test-transformer](#module_jy-transform_unit-test_test-transformer) ℗ + * [~SRC_YAML](#module_jy-transform_unit-test_test-transformer..SRC_YAML) : string ℗ + * [~SRC_JSON](#module_jy-transform_unit-test_test-transformer..SRC_JSON) : string ℗ + * [~SRC_JS](#module_jy-transform_unit-test_test-transformer..SRC_JS) : string ℗ + * [~TRANSFORMER_TEST_BASE_DIR](#module_jy-transform_unit-test_test-transformer..TRANSFORMER_TEST_BASE_DIR) : string ℗ + + + +### jy-transform:unit-test:test-transformer~SRC_YAML : string ℗ +A YAML source file path. + +**Kind**: inner constant of [jy-transform:unit-test:test-transformer](#module_jy-transform_unit-test_test-transformer) +**Access**: private + + +### jy-transform:unit-test:test-transformer~SRC_JSON : string ℗ +A JSON source file path. + +**Kind**: inner constant of [jy-transform:unit-test:test-transformer](#module_jy-transform_unit-test_test-transformer) +**Access**: private + + +### jy-transform:unit-test:test-transformer~SRC_JS : string ℗ +A JS source file path. + +**Kind**: inner constant of [jy-transform:unit-test:test-transformer](#module_jy-transform_unit-test_test-transformer) +**Access**: private + + +### jy-transform:unit-test:test-transformer~TRANSFORMER_TEST_BASE_DIR : string ℗ +Temporary base dir for writer test output. + +**Kind**: inner constant of [jy-transform:unit-test:test-transformer](#module_jy-transform_unit-test_test-transformer) +**Access**: private + + +## jy-transform:unit-test:test-writer ℗ +This unit test suite checks the validity and correctness of Writer module. + +**Access**: private + + +## jy-transform:unit:helper-constants : Object ℗ +The test suite constants definitions. + +**Access**: private + +* [jy-transform:unit:helper-constants](#module_jy-transform_unit_helper-constants) : Object ℗ + * [~TEST_SUITE_DESCRIPTION_UNIT](#module_jy-transform_unit_helper-constants..TEST_SUITE_DESCRIPTION_UNIT) : string + * [~TEST_SUITE_DESCRIPTION_FUNCTIONAL](#module_jy-transform_unit_helper-constants..TEST_SUITE_DESCRIPTION_FUNCTIONAL) : string + * [~TEST_DATA_DIR](#module_jy-transform_unit_helper-constants..TEST_DATA_DIR) : string ℗ + * [~EXPECTED_VALUE](#module_jy-transform_unit_helper-constants..EXPECTED_VALUE) : string ℗ + + + +### jy-transform:unit:helper-constants~TEST_SUITE_DESCRIPTION_UNIT : string +The unit test suite description for the plugin. + +**Kind**: inner constant of [jy-transform:unit:helper-constants](#module_jy-transform_unit_helper-constants) +**Access**: public + + +### jy-transform:unit:helper-constants~TEST_SUITE_DESCRIPTION_FUNCTIONAL : string +The unit test suite description for the plugin. + +**Kind**: inner constant of [jy-transform:unit:helper-constants](#module_jy-transform_unit_helper-constants) +**Access**: public + + +### jy-transform:unit:helper-constants~TEST_DATA_DIR : string ℗ +The basic test data directory path. + +**Kind**: inner constant of [jy-transform:unit:helper-constants](#module_jy-transform_unit_helper-constants) +**Access**: private + + +### jy-transform:unit:helper-constants~EXPECTED_VALUE : string ℗ +An expected value from source files. + +**Kind**: inner constant of [jy-transform:unit:helper-constants](#module_jy-transform_unit_helper-constants) +**Access**: private + + +## jy-transform:unit:logger : Object ℗ +The test suite logger. + +**Access**: private + +* [jy-transform:unit:logger](#module_jy-transform_unit_logger) : Object ℗ + * [~INDENT](#module_jy-transform_unit_logger..INDENT) : string ℗ + * [~TEST_TMP_DIR](#module_jy-transform_unit_logger..TEST_TMP_DIR) : string ℗ + * [~winstonFileOptions](#module_jy-transform_unit_logger..winstonFileOptions) : Object ℗ + * [.timestamp()](#module_jy-transform_unit_logger..winstonFileOptions.timestamp) ⇒ string + * [~winstonConsoleOptions](#module_jy-transform_unit_logger..winstonConsoleOptions) : Object ℗ + * [.timestamp()](#module_jy-transform_unit_logger..winstonConsoleOptions.timestamp) ⇒ string + * [~logger](#module_jy-transform_unit_logger..logger) + * [~formatter(options)](#module_jy-transform_unit_logger..formatter) ⇒ string ℗ + + + +### jy-transform:unit:logger~INDENT : string ℗ +An indent of 0 SPACEs. + +**Kind**: inner constant of [jy-transform:unit:logger](#module_jy-transform_unit_logger) +**Access**: private + + +### jy-transform:unit:logger~TEST_TMP_DIR : string ℗ +A temporary test directory. + +**Kind**: inner constant of [jy-transform:unit:logger](#module_jy-transform_unit_logger) +**Access**: private + + +### jy-transform:unit:logger~winstonFileOptions : Object ℗ +Options for winston file logging. + +**Kind**: inner constant of [jy-transform:unit:logger](#module_jy-transform_unit_logger) +**Access**: private + + +#### winstonFileOptions.timestamp() ⇒ string +Formats the timestamp as [Date](Date) ISO string prefixed by an indent. + +**Kind**: static method of [winstonFileOptions](#module_jy-transform_unit_logger..winstonFileOptions) +**Returns**: string - - The [Date](Date) ISO string. +**See**: #INDENT + + +### jy-transform:unit:logger~winstonConsoleOptions : Object ℗ +Options for winston console logging. + +**Kind**: inner constant of [jy-transform:unit:logger](#module_jy-transform_unit_logger) +**Access**: private + + +#### winstonConsoleOptions.timestamp() ⇒ string +Overwrites the timestamp by indent. + +**Kind**: static method of [winstonConsoleOptions](#module_jy-transform_unit_logger..winstonConsoleOptions) +**Returns**: string - - The indent only. +**See**: #INDENT + + +### jy-transform:unit:logger~logger +The winston logger. + +**Kind**: inner constant of [jy-transform:unit:logger](#module_jy-transform_unit_logger) +**Access**: protected + + +### jy-transform:unit:logger~formatter(options) ⇒ string ℗ +This function formats the log string by given options to log. + +**Kind**: inner method of [jy-transform:unit:logger](#module_jy-transform_unit_logger) +**Returns**: string - The log string. +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| options | Object | The formatter options. | + + + +## jy-transform:test-unit:index ℗ +This unit test module tests the correct exporting from _./index.js_. + +**Access**: private + + +## jy-transform:test-unit:serialize-utils ℗ +This unit test suite checks the validity and correctness of JS serialization utility methods. + +**Access**: private + + +## jy-transform:test-unit:test-joi-extension-file-utils ℗ +This unit test module tests validation FS helper method. + +**Access**: private + + +## jy-transform:unit-test:test-joi-extensions-identifier-utils ℗ +This unit test suite checks validity and correctness of ES6 identifiers. + +**Access**: private + + +## jy-transform:unit-test:test-options-schema-utils ℗ +This unit test suite checks the validity and correctness of options schema helper methods. + +**Access**: private + + +## jy-transform:unit-test:test-options-schema ℗ +This unit test suite checks the validity and correctness of options schema. + +**Access**: private + +* [jy-transform:unit-test:test-options-schema](#module_jy-transform_unit-test_test-options-schema) ℗ + * [~expectOptionsValidationError(invalidOptions, schema)](#module_jy-transform_unit-test_test-options-schema..expectOptionsValidationError) ℗ + * [~expectOptionsValidationSuccess(validOptions, schema)](#module_jy-transform_unit-test_test-options-schema..expectOptionsValidationSuccess) ℗ + + + +### jy-transform:unit-test:test-options-schema~expectOptionsValidationError(invalidOptions, schema) ℗ +Expect a `ValidationError` for a given options function. + +**Kind**: inner method of [jy-transform:unit-test:test-options-schema](#module_jy-transform_unit-test_test-options-schema) +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| invalidOptions | [ReadOptions](#ReadOptions) \| [WriteOptions](#WriteOptions) | The options which potentially produce the error. | +| schema | Schema | The validation schema. | + + + +### jy-transform:unit-test:test-options-schema~expectOptionsValidationSuccess(validOptions, schema) ℗ +Expect a validation success for a given options. + +**Kind**: inner method of [jy-transform:unit-test:test-options-schema](#module_jy-transform_unit-test_test-options-schema) +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| validOptions | [ReadOptions](#ReadOptions) \| [WriteOptions](#WriteOptions) | The options which should be correct. | +| schema | Schema | The validation schema. | + + + +## readYamlFromfile ⇒ Object ℗ +Reads YAML from file. + +**Kind**: global variable +**Returns**: Object - The read JS object from YAML file. +**Throws**: + +- Error When any I/O error occurs while the source file. + +**Access**: private + +| Param | Type | Description | +| --- | --- | --- | +| file | string | The YAML file source. | + + + +## mkdirAndWrite ℗ +Ensures that all dirs exists for file type `dest` and writes the JS object to file. + +**Kind**: global variable +**Access**: private +**See** + +- [TYPE_YAML](TYPE_YAML) +- [TYPE_JSON](TYPE_JSON) +- [TYPE_JS](TYPE_JS) + + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| object | string | | The object to write into file. | +| dest | string | | The file destination path. | +| target | string | | The target type, one of [ 'yaml' | 'json' | 'js' ]. | +| [forceOverwrite] | boolean | false | Forces overwriting the destination file if `true`. | + + + +## readJsOrJsonFromFile ⇒ Promise.<string> ℗ +Writes a serialized object to file. + +**Kind**: global variable +**Returns**: Promise.<string> - Containing the write success message to handle by caller (e.g. for logging). +**Throws**: + +- Error If serialized JSON file could not be written due to any reason. + +**Access**: private +**See** + +- [TYPE_YAML](TYPE_YAML) +- [TYPE_JSON](TYPE_JSON) +- [TYPE_JS](TYPE_JS) + + +| Param | Type | Description | +| --- | --- | --- | +| object | string | The object to write into file. | +| dest | string | The file destination path. | +| target | string | The target type, one of [ 'yaml' | 'json' | 'js' ]. | +| [forceOverwrite] | boolean | Forces overwriting the destination file if `true`. | + + + +## assertTransformSuccess ℗ +Helper method which asserts the successful transformation. + +**Kind**: global variable +**Access**: private + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| options | [TransformOptions](#TransformOptions) | | The transformation options. | +| [es6] | boolean | true | Whether to use ES6 syntax. | + + + +## assertYamlTransformSuccess +Helper method which asserts the successful transformation. + +**Kind**: global variable + +| Param | Type | Description | +| --- | --- | --- | +| options | Object | The transformation options. | + + + +## transformFunc +Transformation middleware changing value for `foo` property. + +**Kind**: global variable + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | To transform. | + + + +## assertTransformSuccess +Helper method which asserts the successful transformation. + +**Kind**: global variable + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| options | Object | | The transformation options. | +| [es6] | boolean | true | Whether to use ES6 syntax. | + + + +## assertYamlTransformSuccess +Helper method which asserts the successful transformation. + +**Kind**: global variable + +| Param | Type | Description | +| --- | --- | --- | +| options | Object | The transformation options. | + + + +## _jsYaml ⇒ Object ℗ +Creates the options from the given transform function, source and destination path parameters. + +**Kind**: global variable +**Returns**: Object - The options object. +**Access**: private + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| src | string | | The source path. | +| dest | string | | The destination path. | +| [func] | \* | transformFunc | The transform function. | + + + +## fsPromisified ℗ +Promisified `fs` module. + +**Kind**: global constant +**Access**: private + + +## fsPromisified ℗ +Promisified `fs` module. + +**Kind**: global constant +**Access**: private + + +## ReadOptions : object +The configuration properties provided to the `read` function. + +**Kind**: global typedef +**Access**: public +**Properties** + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| src | string \| Stream.Readable \| object | | The source (if `string` type it is treated as a file path). | +| origin | string | "yaml" | The source origin type. | +| imports | string | | The exports name for reading from JS source files or objects only. | + + + +## WriteOptions : object +The configuration properties provided to the `write` function. + +**Kind**: global typedef +**Access**: public +**Properties** + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| dest | string \| Stream.Writable \| object | | The destination (if `string` type it is treated as a file path). | +| target | string | "js" | The destination target type. | +| indent | number | 2 | The indentation value for pretty-print of output. | +| exports | string | | The exports name for usage in JS destination files only. | +| force | string | false | Force overwriting of existing output files on write phase. | +| no-es6 | boolean | false | Whether not to use ECMAScript6 syntax for JS type output like `module.exports` instead of `export default`, applicable only for JS output. | +| no-single | boolean | false | Whether _not_ to use single-quotes style for values in JS type output (i.e. double-quotes). | + + + +## TransformOptions : object +The configuration properties provided to the `transform` function. + +**Kind**: global typedef +**Access**: public +**Properties** + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| src | string \| Stream.Readable \| object | | The _read_ source (if `string` type it is treated as a file path). | +| origin | string | "yaml" | The _read_ source origin type. | +| imports | string | | The _read_ exports name for reading from JS source files or objects only. | +| transform | function | | The option is a _transformation_ function with the following signature:

``` [async|Promise] function(object) ``` | +| dest | string \| Stream.Writable \| object | | The _write_ destination (if `string` type it is treated as a file path). This property could be optional in case we infer a value from `src` which is then either a string or a file stream where can get the file path from. If this detection process cannot be fulfilled then the property is `undefined` and the transform process will fail with a `ValidationError` on write phase. | +| target | string | "js" | The _write_ target type. | +| indent | number | 2 | The _write_ indentation value for pretty-print of output. | +| exports | string | | The _write_ exports name for usage in JS destination files only. | +| force | string | false | Force overwriting of existing output files on write phase. | +| no-es6 | boolean | false | Whether not to use ECMAScript6 syntax for JS type output like `module.exports` instead of `export default`, applicable only for JS output. | +| no-single | boolean | false | Whether _not_ to use single-quotes style for values in JS type output (i.e. double-quotes). | + + + +## joi ℗ +Hapi.js Joi. + +**Kind**: global external +**Access**: private +**See**: [Hapi Joi](https://github.com/hapijs/joi) + +* [joi](#external_joi) ℗ + * [.ValidationError](#external_joi.ValidationError) + * [.Schema](#external_joi.Schema) ℗ + * [.Extension](#external_joi.Extension) ℗ + * [.validate](#external_joi.validate) : function ℗ + + + +### joi.ValidationError +Joi validation error. + +**Kind**: static typedef of [joi](#external_joi) +**Access**: public +**See**: [Joi errors](hhttps://github.com/hapijs/joi/blob/v10.2.0/API.md#errors) + + +### joi.Schema ℗ +The validation schema. Can be a [joi](#external_joi) type object or a plain object +where every key is assigned a [joi](#external_joi) type object. + +**Kind**: static typedef of [joi](#external_joi) +**Access**: private +**See**: [Joi API](https://github.com/hapijs/joi/blob/v10.2.2/API.md#joi) + + +### joi.Extension ℗ +Hapi.js Joi schema extension. + +**Kind**: static typedef of [joi](#external_joi) +**Access**: private +**See**: [Hapi Joi Extension](https://github.com/hapijs/joi/blob/v10.2.2/API.md#extendextension) + + +### joi.validate : function ℗ +Joi `validate` method. + +**Kind**: static typedef of [joi](#external_joi) +**Access**: private +**See**: [Joi.validate](https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback) diff --git a/API-PUBLIC.md b/API-PUBLIC.md new file mode 100644 index 0000000..074ecba --- /dev/null +++ b/API-PUBLIC.md @@ -0,0 +1,279 @@ + + +## TOC + +- [Modules](#modules) +- [Typedefs](#typedefs) +- [jy-transform](#jy-transform) +- [ReadOptions : object](#readoptions--codeobjectcode) +- [WriteOptions : object](#writeoptions--codeobjectcode) +- [TransformOptions : object](#transformoptions--codeobjectcode) + + + +## Modules + +

+
jy-transform
+

This module provides the public interface for the read, write and transform functionality.

+
+
+ +## Typedefs + +
+
ReadOptions : object
+

The configuration properties provided to the read function.

+
+
WriteOptions : object
+

The configuration properties provided to the write function.

+
+
TransformOptions : object
+

The configuration properties provided to the transform function.

+
+
+ + + +## jy-transform +This module provides the _public_ interface for the _read_, _write_ and _transform_ functionality. + +**Access**: public +**Author**: Jens Krefeldt + +* [jy-transform](#module_jy-transform) + * [~transform](#module_jy-transform..transform) ⇒ Promise + * [~read](#module_jy-transform..read) ⇒ Promise + * [~write](#module_jy-transform..write) ⇒ Promise + * [~TYPE_YAML](#module_jy-transform..TYPE_YAML) : string + * [~TYPE_JS](#module_jy-transform..TYPE_JS) : string + * [~TYPE_JSON](#module_jy-transform..TYPE_JSON) : string + + + +### jy-transform~transform ⇒ Promise +The entry method for all transformations accepting a configuration object and +an (optional) callback function. It executes the transformation logic. + +1. Input (read) +2. Transform [ + callback] +3. Output (write). + +**Kind**: inner constant of [jy-transform](#module_jy-transform) +**Returns**: Promise - The transformation result. +**Access**: public +**Resolve**: string With the transformation result as message (e.g. to be logged by caller). +**Reject**: ValidationError If any `options` validation occurs. +**Reject**: Error Will throw any error if read, transform or write operation failed due to any reason. + +| Param | Type | Description | +| --- | --- | --- | +| options | [TransformOptions](#TransformOptions) | The configuration for a transformation. | + +**Example** +```js +import { transform } from 'jy-transform'; +const options = { + src: 'foo/bar.yaml', // From YAML file... + transform: async (object) => { // ...callback with exchanging value... + object.foo = 'new value'; + return object; + }, + target: 'foo/bar-transformed.json', // ...to a new JSON file. + indent: 4, +}; + +// ---- Promise style: + +transform(options) + .then(console.log) + .catch(console.error); + + +// ---- async/await style: + +try { + const msg = await transform(options); + console.log(msg); +} catch (err) { + console.error(err.stack); +}; +``` + + +### jy-transform~read ⇒ Promise +Reads a particular content type from a source provided in the passed `options`. + +**Kind**: inner constant of [jy-transform](#module_jy-transform) +**Returns**: Promise - The result. +**Access**: public +**Resolve**: string Resolves with JS object result. +**Reject**: ValidationError If any `options` validation occurs. +**Reject**: Error If any write error occurs. + +| Param | Type | Description | +| --- | --- | --- | +| options | [ReadOptions](#ReadOptions) | The read options. | + +**Example** +```js +import { read } from 'jy-transform'; + + +// --- from file path + +options = { + src: 'foo.yml' +}; + +read(options) + .then(obj => console.log(JSON.stringify(obj))) + .catch(console.error); + + +// --- from Readable + +options = { + src: fs.createReadStream('foo.yml') +}; + +read(options) + .then(obj => console.log(JSON.stringify(obj))) + .catch(console.error); +``` + + +### jy-transform~write ⇒ Promise +Writes the passed JS object to a particular destination described by the passed `options`. + +**Kind**: inner constant of [jy-transform](#module_jy-transform) +**Returns**: Promise - The result. +**Access**: public +**Resolve**: string With the write success message. +**Reject**: Error If any write error occurs. +**Reject**: ValidationError If any `options` validation occurs. + +| Param | Type | Description | +| --- | --- | --- | +| object | Object | The JS source object to write. | +| options | [WriteOptions](#WriteOptions) | The write options. | + +**Example** +```js +import { write } from 'jy-transform'; + + +// ---- write obj to file --- + +const obj = {...}; +const options = { + dest: 'result.js', + indent: 4 +} + +write(obj, options) + .then(console.log) + .catch(console.error); + + +// ---- write obj to Writable --- + +options = { + dest: fs.createWriteStream('result.json'), + indent: 4 +} + +write(obj, options) + .then(console.log) + .catch(console.error); + + +// ---- write obj to object --- + +options = { + dest: {}, + indent: 4 +} + +write(obj, options) + .then(console.log) + .catch(console.error); +``` + + +### jy-transform~TYPE_YAML : string +The `'yaml'` type constant. + +**Kind**: inner constant of [jy-transform](#module_jy-transform) +**Access**: public + + +### jy-transform~TYPE_JS : string +The `'js'` type constant. + +**Kind**: inner constant of [jy-transform](#module_jy-transform) +**Access**: public + + +### jy-transform~TYPE_JSON : string +The `'json'` type constant. + +**Kind**: inner constant of [jy-transform](#module_jy-transform) +**Access**: public + + +## ReadOptions : object +The configuration properties provided to the `read` function. + +**Kind**: global typedef +**Access**: public +**Properties** + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| src | string \| Stream.Readable \| object | | The source (if `string` type it is treated as a file path). | +| [origin] | string | "yaml" | The source origin type. | +| [imports] | string | | The exports name for reading from JS source files or objects only. | + + + +## WriteOptions : object +The configuration properties provided to the `write` function. + +**Kind**: global typedef +**Access**: public +**Properties** + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| dest | string \| Stream.Writable \| object | | The destination (if `string` type it is treated as a file path). | +| [target] | string | "js" | The destination target type. | +| [indent] | number | 2 | The indentation value for pretty-print of output. | +| [exports] | string | | The exports name for usage in JS destination files only. | +| [force] | boolean | false | Force overwriting of existing output files on write phase. | +| [no-es6] | boolean | false | Whether not to use ECMAScript6 syntax for JS type output like `module.exports` instead of `export default`, applicable only for JS output. | +| [no-single] | boolean | false | Whether _not_ to use single-quotes style for values in JS type output (i.e. double-quotes). | + + + +## TransformOptions : object +The configuration properties provided to the `transform` function. + +**Kind**: global typedef +**Access**: public +**Properties** + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| src | string \| Stream.Readable \| object | | The _read_ source (if `string` type it is treated as a file path). | +| [origin] | string | "yaml" | The _read_ source origin type. | +| [imports] | string | | The _read_ exports name for reading from JS source files or objects only. | +| [transform] | function | | The option is a _transformation_ function with the following signature:

``` [async|Promise] function(object) ``` | +| [dest] | string \| Stream.Writable \| object | | The _write_ destination (if `string` type it is treated as a file path). This property could be optional in case we infer a value from `src` which is then either a string or a file stream where can get the file path from. If this detection process cannot be fulfilled then the property is `undefined` and the transform process will fail with a `ValidationError` on write phase. | +| [target] | string | "js" | The _write_ target type. | +| [indent] | number | 2 | The _write_ indentation value for pretty-print of output. | +| [exports] | string | | The _write_ exports name for usage in JS destination files only. | +| [force] | boolean | false | Force overwriting of existing output files on write phase. | +| [no-es6] | boolean | false | Whether not to use ECMAScript6 syntax for JS type output like `module.exports` instead of `export default`, applicable only for JS output. | +| [no-single] | boolean | false | Whether _not_ to use single-quotes style for values in JS type output (i.e. double-quotes). | + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3ff572b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,145 @@ +# Changelog + +## v3.0.0 + +### Note +This release has undergone a huge refactoring which includes some new features, cleanups in many places +including a new public interface, bugfixes and many internal improvements. Of course, this resulted in some +APIs and CLI backwards compatibilities (these are marked in the descriptions below). So, please read the following +changelog entries carefully and see also the following documents for more information about how to use the +new interface: +- [README.md](https://github.com/deadratfink/jy-transform/blob/master/README.md) +- [API-PUBLIC.md](https://github.com/deadratfink/jy-transform/blob/master/API-PUBLIC.md) + +> **IMPORTANT NOTE: Backwards Incompatible - Node.js version >= v6.0.0 required!** + +### New Features: +- [[#64](https://github.com/deadratfink/jy-transform/issues/64)] Support ES6 for JS output. + - **CLI & API Backwards Incompatible Change!** + - This is the default now: + - Generation of `export default` instead of `module.exports`. + - Generation of `export const foo` instead of `module.exports.foo`. + - Can be suppressed by `options[no-es6] = true` (default `false`). +- [[#62](https://github.com/deadratfink/jy-transform/issues/62)] The `options.transform` function (formerly aka + _middleware_ function) is no longer necessary to be a Promise/`async` one. +- [[#59](https://github.com/deadratfink/jy-transform/issues/59)] Support single-quotes options for JS output. + - **CLI & API Backwards Incompatible Change!** + - This is the default now. +- [[#55](https://github.com/deadratfink/jy-transform/issues/55)] The `read` process returns a clone of the + `options.src` when it is a JS object (so the origin would never be changed). +- [[#35](https://github.com/deadratfink/jy-transform/issues/35)] Support for adding `'use strict';` in JS output + file using `options.strict` (default is `false`). + +### Bugfixes: +- [[#57](https://github.com/deadratfink/jy-transform/issues/57)] The minimum indent for YAML target types is + validated for 2 now and throws a `ValidationError` if < 2 (for others 0 is still valid). +- [[#56](https://github.com/deadratfink/jy-transform/issues/56)] If _destination_ is not given on transformation + process but the _target_ is, then the destination's file extension + is adapted to the proper type, e.g. `$ jyt inch.json -t yaml` results in a file _inch.yaml_ (formerly: + _inch.json_ with YAML content or respectively _inch(1).json_ with YAML, the latter if `options.force` was `true`). + +### Public Interface Changes & Improvements: +- [[#61](https://github.com/deadratfink/jy-transform/issues/61)] Invalid indention setting should raise an error: + - **CLI & API Backwards Incompatible Change!** + - An invalid indention setting (i.e. `indent` < 0 or `indent` > 8) raises a `ValidationError` now instead + of using default. +- [[#60](https://github.com/deadratfink/jy-transform/issues/60)] Default `options.indent` is 2 (instead of 4): + - **CLI & API Backwards Incompatible Change!** + - This seems to be more common in the JS/Node.js community. +- [[#57](https://github.com/deadratfink/jy-transform/issues/57)] Provide a simplified interface: + - **API Backwards Incompatible Changes!** + - The exported constants `YAML`, `JS` and `JSON` (usable for `options.origin/target`) are renamed respectively + to `TYPE_YAML`, `TYPE_JS` and `TYPE_JSON`. + - Prototype approach removed from `Transformer`, `Reader` and `Writer`, turning it to internal modules which + exports _named_ functions instead: + - The formerly exported `Reader.readJs(...)/readYaml(...)` functions are not public anymore and replaced + by a general `read(options)` function. + - The formerly exported `Writer.writeJs(...)/writeJson(...)/writeYaml(...)` functions are not public + anymore and replaced by a general `write(object, options)` function. + - The formerly exported `Transformer.transform(options, middleware)` functions + does not take the `middleware` parameter anymore (it is added to options: `options.transform`). + - The formerly exported `middleware` (identity function) is not publicly available anymore. + - Reduced and named export of constants: `TYPE_YAML`, `TYPE_JS` and `TYPE_JSON` only. + - Removal of `LogWrapper` (no more logger injection possible). + - The `options.imports/exports` are not allowed to be empty strings anymore (semantically senseless, just leave it out). + - Of course, the configuration property `options.dest` is required for _write_ process when using the API (but not from _transformer_ + if it can be inferred from `options.src` but which is true for string (file) or file-based stream sources only). + +### Internal Changes & Improvements: +- [[#63](https://github.com/deadratfink/jy-transform/issues/63)] [Greenkeeper](https://greenkeeper.io/) integrated. +- [[#54](https://github.com/deadratfink/jy-transform/issues/54)] General dependency check and update: + - Latest versions. + - Usage of _native_ Promises instead of [bluebird](http://bluebirdjs.com/docs/getting-started.html). + - Test dependencies reduced. +- [[#53](https://github.com/deadratfink/jy-transform/issues/53)] Update supported node versions: + - Add travis build for Node.js v8.x and v9.x. + - **CLI & API Backwards Incompatible Change!** + - Remove travis build for Node.js < v6.x. +- [[#52](https://github.com/deadratfink/jy-transform/issues/52)] Leverage modern ES6 features: + - Integrated by [babel](https://babeljs.io/). + - Update of dependencies and amount reduced. + - Code base could be shrinked and readabilty was improved. +- [[#51](https://github.com/deadratfink/jy-transform/issues/51)] Removal of _development_ branch. +- [[#50](https://github.com/deadratfink/jy-transform/issues/50)] Update/upgrade ESLint +- [[#49](https://github.com/deadratfink/jy-transform/issues/49)] Tests re-written in + [Jest](https://facebook.github.io/jest), could get rid of "complex" test setup + ([assert](https://github.com/defunctzombie/commonjs-assert), [mocha](https://mochajs.org/) and + [istanbul](https://github.com/gotwarlost/istanbul)). +- [[#48](https://github.com/deadratfink/jy-transform/issues/48)] Using [Joi](https://github.com/hapijs/joi) + for consistent options validation: + - Removal of `OptionsHandler` and `Validator`. +- [[#46](https://github.com/deadratfink/jy-transform/issues/46)] Use Make as an abstraction to npm scripts. +- [[#45](https://github.com/deadratfink/jy-transform/issues/45)] [Node Security Plattform](https://nodesecurity.io/orgs/deadratfink/projects/7ac99a62-a8c4-4321-8d57-8a5e542f04f0) integrated. +- [[#43](https://github.com/deadratfink/jy-transform/issues/43)] Documentation restructured. + + +### v2.0.1 + +- [[#39](https://github.com/deadratfink/jy-transform/issues/39)] Maintenance release. + - Update dependencies to latest. + - Add travis build for Node.js v7.x and v6.x. + - Docs improved/corrected. + - Add target pretest in `scripts` section to `rm` _./test/tmp_ folder. + +### v2.0.0 + +- [[#33](https://github.com/deadratfink/jy-transform/issues/33)] Enhance `LogWrapper` with `TRACE` level (API). +- [[#32](https://github.com/deadratfink/jy-transform/issues/32)] Introduce input and output on CLI as + ARGS instead of OPTIONS + (non-backwards compatible change for CLI usage, _no_ impact on API level!), e.g. on CLI type in + `$ jyt.js foo.js bar.yaml` instead of `$ jyt.js -s foo.js -d bar.yaml`. +- [[#31](https://github.com/deadratfink/jy-transform/issues/31)] Bugfix: given `Object` source results in + 'yaml' for origin (API). +- [Cleanup] Update dependencies. + +### v1.0.2 + +- [[#30](https://github.com/deadratfink/jy-transform/issues/30)] Fix README and externalize API reference to wiki. +- [[#29](https://github.com/deadratfink/jy-transform/issues/29)] Fix Promise warning on write process. + +### v1.0.1 + +Initial public release. This covers the basic implementation and tests. The following features and fixes and +part of this release: +- [[#27](https://github.com/deadratfink/jy-transform/issues/27)] Export variable for JS input. +- [[#22](https://github.com/deadratfink/jy-transform/issues/22)] Integrate Coveralls. +- [[#21](https://github.com/deadratfink/jy-transform/issues/21)] Check and fix CodeClimate issues. +- [[#20](https://github.com/deadratfink/jy-transform/issues/20)] Cleanup test dir. +- [[#19](https://github.com/deadratfink/jy-transform/issues/19)] File overwrite switch (`-f`, `-force`). +- [[#18](https://github.com/deadratfink/jy-transform/issues/18)] Read and Write from other sources than file path. +- [[#16](https://github.com/deadratfink/jy-transform/issues/16)] ERROR: Error: Invalid target option found while + creating destination file extension. +- [[#15](https://github.com/deadratfink/jy-transform/issues/15)] Measure test code coverage and add a badge. +- [[#12](https://github.com/deadratfink/jy-transform/issues/12)] Create middleware collection file to use by + clients and internally. +- [[#11](https://github.com/deadratfink/jy-transform/issues/11)] Check all Promises for optimization possibilities. +- [[#10](https://github.com/deadratfink/jy-transform/issues/10)] Integrate project with Travis. +- [[#9](https://github.com/deadratfink/jy-transform/issues/9)] Resolve origin and target from file extension + whenever possible. +- [[#8](https://github.com/deadratfink/jy-transform/issues/8)] Enable JS reading with `require(...)`. +- [[#7](https://github.com/deadratfink/jy-transform/issues/7)] YAML indent is not set to `Constants.MIN_YAML_INDENT` + when indent is set to 0. +- [[#6](https://github.com/deadratfink/jy-transform/issues/6)] Finish full JSDoc for all methods. +- [[#5](https://github.com/deadratfink/jy-transform/issues/5)] Write unit tests. +- [[#4](https://github.com/deadratfink/jy-transform/issues/4)] Export variable for JS output. +- [[#3](https://github.com/deadratfink/jy-transform/issues/3)] Promise array as middleware solved with `Promise.all([...])`. diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..d166473 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,7 @@ +# See: https://github.com/blog/2392-introducing-code-owners +# +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in the repo. +* @deadratfink diff --git a/MAKE.md b/MAKE.md new file mode 100644 index 0000000..7ed89d0 --- /dev/null +++ b/MAKE.md @@ -0,0 +1,14 @@ +Target Call | Description | Dependencies +---|---|--- +`$ make` | This calls the default target `help`. | +`$ make bithound` | Runs bithound check. | +`$ make build` | Babel transpiles files from _./src_ to _./lib_. | +`$ make clean` | Removes generated files in folders ./node_modules, ./lib and ./coverage" | +`$ make eslint` | Runs ESLint. | +`$ make help` | Prints the help about targets. | +`$ make inch` | Runs [inch](https://github.com/rrrene/inchjs) for api-docs check. **NOTE | `build` +`$ make install` | Installs all modules | +`$ make nsp` | Runs an [Node Security Plattform](https://nodesecurity.io/opensource) check. | +`$ make publish` | Publishes module to NPM registry. | `test readme` +`$ make readme` | Creates all the documentation parts of the project | +`$ make test` | Runs the test suite, ESLint and a [Node Security Plattform](https://nodesecurity.io/opensource) check. | `build` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a9081cb --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +.PHONY: test clean readme + +.DEFAULT_GOAL:=help + +build: ## Babel transpiles files from _./src_ to _./lib_. + @printf "Transpiling files from ./src to ./lib (babel)...\n" + npm run build + +install: ## Installs all modules + @printf "Install all modules...\n" + npm install + +clean: ## Removes generated files in folders ./node_modules, ./lib and ./coverage" + @printf "Removing ./lib, ./node_modules, ./lib and ./coverage...\n" + rm -rf lib + rm -rf node_modules + rm -rf coverage + +test: build ## Runs the test suite, ESLint and a [Node Security Plattform](https://nodesecurity.io/opensource) check. + @printf "Running test suite, ESLint and NSP...\n" + npm test + npm run eslint + npm run nsp + +inch: build ## Runs [inch](https://github.com/rrrene/inchjs) for api-docs check. **NOTE: due to some bug in inch it does not work at the moment!** + @printf "Running inch to check api-docs status...\n" + npm run inch + +nsp: ## Runs an [Node Security Plattform](https://nodesecurity.io/opensource) check. + @printf "Running NSP check...\n" + npm run nsp + +readme: ## Creates all the documentation parts of the project: _README.md_, _MAKE.md_, _PACKAGE.md_ and _API.md_ (the latter based on [JSDoc](http://usejsdoc.org/)). + @printf "Create documentation...\n" + npm run readme + +publish: test readme ## Publishes module to NPM registry. + @printf "Publish module to NPM repo...\n" + npm publish + +eslint: ## Runs ESLint. + @printf "Running ESLint...\n" + npm run eslint + +bithound: ## Runs bithound check. + @printf "Running bithound check...\n" + npm run bithound + +help: ## Prints the help about targets. + @printf "Usage: make [\033[34mtarget\033[0m]\n" + @printf "Default: \033[34m%s\033[0m\n" $(.DEFAULT_GOAL) + @printf "Targets:\n" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[34m%-14s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort diff --git a/PACKAGE.md b/PACKAGE.md new file mode 100644 index 0000000..82ef1ce --- /dev/null +++ b/PACKAGE.md @@ -0,0 +1,67 @@ +# jy-transform + +This project aims to read, write and transform YAML, JS or JSON objects into each other using CLI or API, while the source and destination resources can be files on CLI and additionally, objects or streams on API level. + +## Installation + +```sh +npm install jy-transform --global +``` + + +## Tests + +```sh +npm install +npm test +``` + +## Dependencies + +- [babel-runtime](): babel selfContained runtime +- [cli](https://github.com/node-js-libs/cli): A tool for rapidly building command line apps +- [is-stream](): Check if something is a Node.js stream +- [joi](): Object schema validation +- [js-yaml](): YAML 1.2 parser and serializer +- [json-stringify-safe](https://github.com/isaacs/json-stringify-safe): Like JSON.stringify, but doesn't blow up on circular refs. +- [mkdirp-then](): mkdirp as promised +- [promisify-es6](https://github.com/manuel-di-iorio/promisify-es6): Promisify callback-style functions to ES6 promises +- [serialize-js](https://github.com/RReverser/serialize-js): User-readable object serialization for JavaScript. + +## Dev Dependencies + +- [babel-cli](): Babel command line. +- [babel-eslint](https://github.com/babel/babel-eslint): Custom parser for ESLint +- [babel-plugin-transform-flow-strip-types](): Strip flow type annotations from your output code. +- [babel-plugin-transform-runtime](): Externalise references to helpers and builtins, automatically polyfilling your code without polluting globals +- [babel-preset-env](): A Babel preset for each environment. +- [bithound](https://github.com/bithound/cli.bithound.io): Commands for interacting with bitHound: https://bithound.io +- [chalk](): Terminal string styling done right +- [codacy-coverage](https://github.com/codacy/node-codacy-coverage): Code Coverage reporter for Codacy.com +- [codeclimate-test-reporter](https://github.com/codeclimate/javascript-test-reporter): Code Climate test reporter client for javascript projects +- [codecov](https://github.com/codecov/codecov-node): Uploading report to Codecov: https://codecov.io +- [coveralls](https://github.com/nickmerwin/node-coveralls): takes json-cov output into stdin and POSTs to coveralls.io +- [cwd](): Easily get the CWD (current working directory) of a project based on package.json, optionally starting from a given path. (node.js/javascript util) +- [doctoc](https://github.com/thlorenz/doctoc): Generates TOC for markdown files of local git repo. +- [eslint](): An AST-based pattern checker for JavaScript. +- [eslint-config-airbnb-base](https://github.com/airbnb/javascript): Airbnb's base JS ESLint config, following our styleguide +- [eslint-plugin-filenames](https://github.com/selaux/eslint-plugin-filenames): Eslint rule for consistent filenames. +- [eslint-plugin-import](https://github.com/benmosher/eslint-plugin-import): Import with sanity. +- [eslint-plugin-jest](): Eslint rules for Jest +- [eslint-plugin-jest-async](https://github.com/deadratfink/jy-transform.git): ESLint plugin to detect improper Jest test assertions for asynchronous (Promise-based) actions +- [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc): JSDoc linting rules for ESLint. +- [eslint-plugin-json](https://github.com/azeemba/eslint-plugin-json): Lint JSON files +- [fs-extra](https://github.com/jprichardson/node-fs-extra): fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as mkdir -p, cp -r, and rm -rf. +- [inchjs](https://github.com/rrrene/inchjs): JS Wrapper for Inch for JavaScript +- [jest](https://github.com/facebook/jest): Delightful JavaScript Testing. +- [jsdoc-babel](https://github.com/ctumolosus/jsdoc-babel): A JSDoc plugin that transforms ES6 source files with Babel before they are processsed. +- [jsdoc-parse](): Transforms jsdoc data into something more suitable for use as template input +- [jsdoc-to-markdown](): Generates markdown API documentation from jsdoc annotated source code +- [nsp](https://github.com/nodesecurity/nsp): The Node Security (nodesecurity.io) command line interface +- [package-json-to-readme](): Generate a README.md from package.json contents +- [winston](https://github.com/winstonjs/winston): A multi-transport async logging library for Node.js + + +## License + +MIT diff --git a/README.md b/README.md index 069673d..59afd97 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,235 @@ -![jy-transform logo](https://github.com/deadratfink/jy-transform/blob/master/image/jytransform.png) - -# Stats - -[![Greenkeeper badge](https://badges.greenkeeper.io/deadratfink/jy-transform.svg)](https://greenkeeper.io/) - -## General - -| [License](https://github.com/deadratfink/jy-transform/blob/master/LICENSE.md) | [Issues](https://github.com/deadratfink/jy-transform/issues) | [Releases](https://github.com/deadratfink/jy-transform/releases) | [Tags](https://github.com/deadratfink/jy-transform/tags) | [Travis CI](https://travis-ci.org) | [Waffle](https://waffle.io/deadratfink/jy-transform) | [Code Climate](https://codeclimate.com/github/deadratfink/jy-transform) | -| --- | --- | --- | --- | --- | --- | --- | -| [![License][gh-license-image]][gh-license-url] | [![Issue Stats][gh-issues-image]][gh-issues-url] | [![Releases][gh-releases-image]][gh-releases-url] | [![Tags][gh-tags-image]][gh-tags-url] | [![Build Status][ci-image]][ci-url] | [![Waffle][waffle-image]][waffle-url] | [![Code Climate][cocl-image]][cocl-url] | - -## Branches - -| Branch | [Codecov](https://codecov.io) | [Coveralls](https://coveralls.io) | [Inch CI](http://inch-ci.org) | [David](https://david-dm.org) DM | [David](https://david-dm.org) DM (dev) | -| --- | --- | --- | --- | --- | --- | -| master | [![codecov.io][cc-image-master]][cc-url-master] | [![coveralls.io][ca-image-master]][ca-url-master] | [![inch-ci.org][inch-image-master]][inch-url-master] | [![Dependency Status][dep-image-master]][dep-url-master] | [![devDependency Status][devdep-image-master]][devdep-url-master] | -| development | [![codecov.io][cc-image-development]][cc-url-development] | [![coveralls.io][ca-image-development]][ca-url-development] | [![inch-ci.org][inch-image-development]][inch-url-development] | [![Dependency Status][dep-image-development]][dep-url-development] | [![devDependency Status][devdep-image-development]][devdep-url-development] | - -### Coverage - -| master | development | -| --- | --- | -| ![codecov.io](https://codecov.io/gh/deadratfink/jy-transform/branch/master/graphs/tree.svg) | ![codecov.io](https://codecov.io/gh/deadratfink/jy-transform/branch/development/graphs/tree.svg) | - - - - - - -## NPM - -[![NPM](https://nodei.co/npm/jy-transform.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/jy-transform/) -[![NPM](https://nodei.co/npm-dl/jy-transform.png?height=3&months=9)](https://nodei.co/npm-dl/jy-transform/) - -[gh-license-image]: https://img.shields.io/github/license/deadratfink/jy-transform.svg?style=flat-square +[![Node version][node-version-image]][node-version-url] +[![License][gh-license-image]][gh-license-url] +[![Issue Stats][gh-issues-image]][gh-issues-url] +[![Releases][gh-releases-image]][gh-releases-url] +[![Tags][gh-tags-image]][gh-tags-url] +[![Build Status][ci-image]][ci-url] +[![Waffle][waffle-ready-image]][waffle-url] +[![Waffle][waffle-waffle-in-progress-image]][waffle-url] +[![Code Climate][cocl-image]][cocl-url] +[![codecov.io][cc-image-master]][cc-url-master] +[![coveralls.io][ca-image-master]][ca-url-master] +[![inch-ci.org][inch-image-master]][inch-url-master] +[![bitHound Overall Score](https://www.bithound.io/github/deadratfink/jy-transform/badges/score.svg)](https://www.bithound.io/github/deadratfink/jy-transform) +[![bitHound Code][bithound-code-image]][bithound-url] +[![bitHound Dependencies][bitHound-dependencies-image]][bitHound-dependencies] +[![bitHound Dev Dependencies][bitHound-dev-dependencies-image]][bitHound-dependencies] +[![Codacy Badge](https://img.shields.io/codacy/grade/c2ebaac0f9874062ba468ff6bd7edc4e.svg?style=flat)](https://www.codacy.com/app/deadratfink/jy-transform?utm_source=github.com&utm_medium=referral&utm_content=deadratfink/jy-transform&utm_campaign=Badge_Grade) +[![Codacy Badge](https://img.shields.io/codacy/coverage/c2ebaac0f9874062ba468ff6bd7edc4e.svg?style=flat)](https://www.codacy.com/app/deadratfink/jy-transform?utm_source=github.com&utm_medium=referral&utm_content=deadratfink/jy-transform&utm_campaign=Badge_Coverage) +[![Greenkeeper badge](https://badges.greenkeeper.io/deadratfink/jy-transform.svg?style=flat)](https://greenkeeper.io/) +[![NSP Status][nsp-image-master]][nsp-url-master] +[![HitCount](http://hits.dwyl.io/deadratfink/jy-transform.svg?style=flat)](http://hits.dwyl.io/deadratfink/jy-transform) + + + + +[![NPM][npm-image]][npm-url] + + +[gh-license-image]: https://img.shields.io/github/license/deadratfink/jy-transform.svg?style=flat [gh-license-url]: https://github.com/deadratfink/jy-transform/blob/master/LICENSE.md -[gh-issues-image]: https://img.shields.io/github/issues/deadratfink/jy-transform.svg?style=flat-square +[gh-issues-image]: https://img.shields.io/github/issues/deadratfink/jy-transform.svg?style=flat [gh-issues-url]: https://github.com/deadratfink/jy-transform/issues -[gh-releases-image]: https://img.shields.io/github/release/deadratfink/jy-transform.svg?style=flat-square +[gh-releases-image]: https://img.shields.io/github/release/deadratfink/jy-transform.svg?style=flat [gh-releases-url]: https://github.com/deadratfink/jy-transform/releases -[gh-tags-image]: https://img.shields.io/github/tag/deadratfink/jy-transform.svg?style=flat-square +[gh-tags-image]: https://img.shields.io/github/tag/deadratfink/jy-transform.svg?style=flat [gh-tags-url]: https://github.com/deadratfink/jy-transform/tags -[ci-image]: https://img.shields.io/travis/deadratfink/jy-transform.svg?style=flat-square +[ci-image]: https://img.shields.io/travis/deadratfink/jy-transform.svg?style=flat [ci-url]: https://travis-ci.org/deadratfink/jy-transform/branches -[is-pull-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/pr?style=flat-square -[is-issue-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/issue?style=flat-square +[is-pull-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/pr?style=flat +[is-issue-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/issue?style=flat [is-url]: http://issuestats.com/github/deadratfink/jy-transform -[waffle-image]: https://badge.waffle.io/deadratfink/jy-transform.png?label=ready&title=Ready&style=flat-square +[waffle-ready-image]: https://img.shields.io/waffle/label/deadratfink/jy-transform.svg?label=ready&title=Waffle%20Ready&style=flat +[waffle-waffle-in-progress-image]: https://img.shields.io/waffle/label/deadratfink/jy-transform.svg?label=in%20progress&title=Waffle%20In%20Progress&style=flat [waffle-url]: https://waffle.io/deadratfink/jy-transform -[cocl-image]: https://img.shields.io/codeclimate/github/deadratfink/jy-transform.svg?style=flat-square +[cocl-image]: https://img.shields.io/codeclimate/github/deadratfink/jy-transform.svg?style=flat [cocl-url]: https://codeclimate.com/github/deadratfink/jy-transform -[cc-image-master]: https://img.shields.io/codecov/c/github/deadratfink/jy-transform/master.svg?style=flat-square +[cc-image-master]: https://img.shields.io/codecov/c/github/deadratfink/jy-transform/master.svg?style=flat [cc-url-master]: https://codecov.io/github/deadratfink/jy-transform?branch=master -[cc-image-development]: https://img.shields.io/codecov/c/github/deadratfink/jy-transform/development.svg?style=flat-square -[cc-url-development]: https://codecov.io/github/deadratfink/jy-transform?branch=development -[ca-image-master]: https://img.shields.io/coveralls/deadratfink/jy-transform/master.svg?style=flat-square +[ca-image-master]: https://img.shields.io/coveralls/deadratfink/jy-transform/master.svg?style=flat [ca-url-master]: https://coveralls.io/github/deadratfink/jy-transform?branch=master -[ca-image-development]: https://img.shields.io/coveralls/deadratfink/jy-transform/development.svg?style=flat-square -[ca-url-development]: https://coveralls.io/github/deadratfink/jy-transform?branch=development -[inch-image-master]: https://inch-ci.org/github/deadratfink/jy-transform.svg?branch=master&style=flat-square +[inch-image-master]: https://inch-ci.org/github/deadratfink/jy-transform.svg?branch=master&style=flat [inch-url-master]: https://inch-ci.org/github/deadratfink/jy-transform?branch=master -[inch-image-development]: https://inch-ci.org/github/deadratfink/jy-transform.svg?branch=development&style=flat-square -[inch-url-development]: https://inch-ci.org/github/deadratfink/jy-transform?branch=development -[dep-image-master]: https://img.shields.io/david/deadratfink/jy-transform/master.svg?style=flat-square +[dep-image-master]: https://img.shields.io/david/deadratfink/jy-transform/master.svg?style=flat [dep-url-master]: https://david-dm.org/deadratfink/jy-transform/master -[dep-image-development]: https://img.shields.io/david/deadratfink/jy-transform/development.svg?style=flat-square -[dep-url-development]: https://david-dm.org/deadratfink/jy-transform/development -[devdep-image-master]: https://img.shields.io/david/dev/deadratfink/jy-transform/master.svg?style=flat-square +[devdep-image-master]: https://img.shields.io/david/dev/deadratfink/jy-transform/master.svg?style=flat [devdep-url-master]: https://david-dm.org/deadratfink/jy-transform/master#info=devDependencies -[devdep-image-development]: https://img.shields.io/david/dev/deadratfink/jy-transform/development.svg?style=flat-square -[devdep-url-development]: https://david-dm.org/deadratfink/jy-transform/development#info=devDependencies + +[nsp-image-master]: https://nodesecurity.io/orgs/deadratfink/projects/7ac99a62-a8c4-4321-8d57-8a5e542f04f0/badge?style=flat +[nsp-url-master]: https://nodesecurity.io/orgs/deadratfink/projects/7ac99a62-a8c4-4321-8d57-8a5e542f04f0 + +[node-version-image]: https://img.shields.io/node/v/jy-transform.svg?style=flat +[node-version-url]: http://nodejs.org/download/ + +[npm-image]: https://nodei.co/npm/jy-transform.png?downloads=true&downloadRank=true&stars=true +[npm-url]: https://nodei.co/npm/jy-transform/ +[npm-downloads-image]: https://nodei.co/npm-dl/jy-transform.png?height=2&months=9 + +[bithound-url]: https://www.bithound.io/github/deadratfink/jy-transform +[bithound-code-image]: https://img.shields.io/bithound/code/github/deadratfink/jy-transform.svg?style=flat +[bitHound-dependencies]: https://www.bithound.io/github/deadratfink/jy-transform/master/dependencies/npm +[bitHound-dependencies-image]: https://img.shields.io/bithound/dependencies/github/deadratfink/jy-transform.svg?style=flat +[bitHound-dev-dependencies-image]: https://img.shields.io/bithound/devDependencies/github/deadratfink/jy-transform.svg?style=flat +[bitHound-score-image]: https://img.shields.io/bithound/score/github/deadratfink/jy-transform.svg?style=flat + + +# jy-transform + +This project aims to read, write and transform YAML, JS or JSON objects into each other using CLI or API, while the source and destination resources can be files on CLI and additionally, objects or streams on API level. + +## Installation + +```sh +npm install jy-transform --global +``` + + -# TOC - -- [jy-transform](#jy-transform) - - [Installation](#installation) - - [Tests](#tests) - - [Dependencies](#dependencies) - - [Dev Dependencies](#dev-dependencies) - - [License](#license) - - [Motivation](#motivation) +## TOC + +- [Why This Module?](#why-this-module) +- [CLI in 3 Seconds](#cli-in-3-seconds) + - [File Transformation](#file-transformation) +- [API in a Minute](#api-in-a-minute) + - [Transformation from Source to Destination](#transformation-from-source-to-destination) + - [Read into JS object from particular Source (File, Stream or JS Object)](#read-into-js-object-from-particular-source-file-stream-or-js-object) + - [Write JS object to particular Destination](#write-js-object-to-particular-destination) - [Usage](#usage) - [Usage Types](#usage-types) - [Use Cases](#use-cases) + - [Origin and Target Type Inference](#origin-and-target-type-inference) - [Limitations](#limitations) - [CLI Usage](#cli-usage) - - [Origin and Target Type Inference](#origin-and-target-type-inference) + - [Examples](#examples) - [API Usage](#api-usage) - - [Using Custom Logger](#using-custom-logger) -- [API Reference](#api-reference) - [Contributing](#contributing) -- [Changelog](#changelog) +- [Further information](#further-information) -# jy-transform +## Why This Module? -This project aims to read, write and transform YAML, JS or JSON objects into each other using CLI or API, while the source and destination resources can be files on CLI and additionally, objects or streams on API level. +After struggling with some huge YAML file and accidentally +occurring wrong indentations which results in an annoying investigation hell, +I decided to get rid of the YAML file and therefore, create a module which +should be aimed as the swiss army knife for transforming YAML, JS and JSON +types into each other format. -## Installation +## CLI in 3 Seconds -Download node at [nodejs.org](http://nodejs.org) and install it, if you haven't already. +### File Transformation -```sh -npm install jy-transform --global +E.g. transform YAML content file to a JSON file with an indention of 4: + +```text +$ jyt foo/bar.yaml -t json -i 4 ``` +## API in a Minute -## Tests +### Transformation from Source to Destination -```sh -npm install -npm test +```javascript +import { transform } from 'jy-transform'; + +const options = { + src: 'foo/bar.yaml', // E.g. read from YAML file... + transform: async (object) => { // ...with exchanging value... + object.foo = 'new value'; + return object; + }, + dest: 'foo/bar-transformed.json', // ...to a new JSON file. + indent: 4, // Ensure an indentation of 4. +}; + +// ---- Promise style: + +transform(options) // Transform, of course, inside an async. + .then(console.log) // Success message! + .catch(console.error); // Oops! + + +// ---- async/await style: + +try { + const msg = await transform(options); // Transform, of course, inside an async. + console.log(msg); // Success message! +} catch (err) { // Oops! + console.error(err); +} ``` -## Dependencies +### Read into JS object from particular Source (File, Stream or JS Object) -- [bluebird](https://github.com/petkaantonov/bluebird): Full featured Promises/A+ implementation with exceptionally good performance -- [cli](https://github.com/node-js-libs/cli): A tool for rapidly building command line apps -- [is-stream](https://github.com/sindresorhus/is-stream): Check if something is a Node.js stream -- [js-yaml](https://github.com/nodeca/js-yaml): YAML 1.2 parser and serializer -- [json-stringify-safe](https://github.com/isaacs/json-stringify-safe): Like JSON.stringify, but doesn't blow up on circular refs. -- [mkdirp-then](https://github.com/fs-utils/mkdirp-then): mkdirp as promised -- [serialize-js](https://github.com/RReverser/serialize-js): User-readable object serialization for JavaScript. +```javascript +import { read } from 'jy-transform'; -## Dev Dependencies +const options = { src: 'foo/bar.yaml' }; // E.g. read from file. -- [codeclimate-test-reporter](https://github.com/codeclimate/javascript-test-reporter): Code Climate test reporter client for javascript projects -- [codecov](https://github.com/codecov/codecov-node): Uploading report to Codecov: https://codecov.io -- [coveralls](https://github.com/nickmerwin/node-coveralls): takes json-cov output into stdin and POSTs to coveralls.io -- [doctoc](https://github.com/thlorenz/doctoc): Generates TOC for markdown files of local git repo. -- [fs-extra](https://github.com/jprichardson/node-fs-extra): fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as mkdir -p, cp -r, and rm -rf. -- [istanbul](https://github.com/gotwarlost/istanbul): Yet another JS code coverage tool that computes statement, line, function and branch coverage with module loader hooks to transparently add coverage when running tests. Supports all JS coverage use cases including unit tests, server side functional tests -- [jsdoc-parse](https://github.com/jsdoc2md/jsdoc-parse): Transforms jsdoc data into something more suitable for use as template input -- [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown): Generates markdown API documentation from jsdoc annotated source code -- [mocha](https://github.com/mochajs/mocha): simple, flexible, fun test framework -- [mocha-lcov-reporter](https://github.com/StevenLooman/mocha-lcov-reporter): LCOV reporter for Mocha -- [object-path](https://github.com/mariocasciaro/object-path): Access deep object properties using a path -- [package-json-to-readme](https://github.com/zeke/package-json-to-readme): Generate a README.md from package.json contents -- [winston](https://github.com/winstonjs/winston): A multi-transport async logging library for Node.js +// ---- Promise style: +read(options) + .then((object) => console.log(JSON.stringify(object))) // Print read object. + .catch(console.error); -## License +// ---- async/await style: -SEE LICENSE IN [LICENSE.md](https://github.com/deadratfink/jy-transform/blob/master/LICENSE.md) +try { + const object = await read(options); + console.log(JSON.stringify(object)); // Print read object. +} catch (err) { + console.error(err); +} +``` -## Motivation +### Write JS object to particular Destination -Why this module? After struggling with some huge YAML file and accidentally -occurring wrong indentions which results in an annoying failure investigation, -I decided to get rid of the YAML file and therefore, create a module which -should be aimed as the swiss army knife for transforming YAML, JS and JSON -types into each other format. +```javascript +import { write } from 'jy-transform'; + +const options = { dest: 'foo/bar.yaml' }; // E.g. write to file. + +// ---- Promise style: + +write(object, options) + .then(console.log) // Print write success message. + .catch(console.error); -# Usage +// ---- async/await style: -The module can be used on CLI or as API (the latter is fully [Promise](http://bluebirdjs.com/docs/api-reference.html) -based). +try { + const msg = await write(object, options); + console.log(msg); // Print write success message. +} catch (err) { + console.error(err); +} +``` + +## Usage -## Usage Types +The module can be used on CLI or as API (the latter is fully +[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)/`async` based). + +### Usage Types Since the module can be used in two different ways, use installation as follows: @@ -184,35 +238,38 @@ Since the module can be used in two different ways, use installation as follows: Both usage types are described in more detail in the following sections. -## Use Cases +### Use Cases So, what are the typical use cases for this module? In terms of _transformation_ these consists of different phases: -- Reading files (`Reader`) -- Transforming JSON objects (`Transformer`) -- Apply dedicated actions on the intermediate JSON objects (`Transformer` + `Middleware`) -- Writing files (`Writer`) + 1. Reading from source + 2. Transforming JSON objects or apply dedicated actions on the intermediate JSON objects + 3. Writing to a destination -### Reading +#### Read Case -Reading from: +Reading from a file: -- _*.yaml_ file -- _*.js_ file -- _*.json_ file +- _*.yaml_ +- _*.js_ +- _*.json_ -Additionally, on API level to a: +Additionally, on API level from: -- `stream.Readable` - - Serialized JSON and YAML - - Requires `options.origin` property set - - Reads as UTF-8 -- JS `object` (actually, this means the reading phase is skipped, because object is in-memory already) +- `stream.Readable` (stream2): + - Contains serialized JS, JSON or YAML + - If not a file stream then setting requires `options.origin` property is mandatory + - Reads as UTF-8 +- JS `Object`: + - Actually, this means the reading phase is "skipped", because object is in-memory already + - Of course, this case _cannot_ not be applied to serialized JSON or YAML content -### Transformation +#### Transformation Case -The transformation can take place into several directions: +The _transformation_ is usually a format change, but can also be refer to content changes on the +intermediate JS object, the latter with the help of a configurable `transform` callback function. +All possible directions are: - YAML ⇒ JS - YAML ⇒ JSON @@ -230,38 +287,51 @@ while: - [JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript) = _*.js_ (JS object) - [JSON](http://json.org) = _*.json_ (JS object serialized as JSON) -### Middleware +As mentioned above a configurable `transform` callback can apply particular actions on the intermediate JS object, but +this is an optional part for [transformation](#transformation) phase. + +#### Write Case -Apply actions on the intermediate JS object via injected [Promise](http://bluebirdjs.com/docs/api-reference.html) -functions. This is an optional part for [transformation](#transformation) phase. +Writing to a file: -### Writing +- _*.yaml_ +- _*.js_ +- _*.json_ -Writing to: +Additionally, on API level to: -- _*.yaml_ file -- _*.js_ file -- _*.json_ file +- `stream.Writable` (stream2) implementations: + - Serialized JS, JSON and YAML + - Requires `options.target` property set + - Writes UTF-8 +- JS `Object`: + - JS as a simple reference + - YAML and JSON as a serialized string -Additionally, on API level to a: +### Origin and Target Type Inference + +This module supports automatic type inference from file extensions as shown by the following table (from-to): + +| File Extension | Type | +| --- | --- | +| _*.yaml_ | _yaml_ | +| _*.yml_ | _yaml_ | +| _*.js_ | _js_ | +| _*.json_ | _json_ | -- `stream.Writable` - - Serialized JS, JSON and YAML - - Requires `options.target` property set - - Writes UTF-8 -- JS `object` +> **NOTE:** if you have files without an extension or e.g. _*.txt_ you _have_ to specify the `origin` or `target` type! -## Limitations +### Limitations - Since this module is build to transform from and to different type formats, any `Function`s residing in JS type objects are _not_ supported, e.g. transforming ```javascript - module.exports = { - fooKey: 'foo', - fooFunction: function foo() { - //... - } + export const foobar = { + fooKey: 'foo', + fooFunction: function foo() { + //... + } } ``` @@ -269,7 +339,7 @@ Additionally, on API level to a: ```json { - "fooKey": "foo" + "fooKey": "foo" } ``` @@ -284,26 +354,23 @@ Additionally, on API level to a: and JS only, but at the moment we require that each document to transform is a _single_ one per source (or in case of JS could be identified)! This feature is planned and reflected in [#14](https://github.com/deadratfink/jy-transform/issues/14). -- Schema validation for input and output is another topic which is planned by - [#1](https://github.com/deadratfink/jy-transform/issues/1) and - [#2](https://github.com/deadratfink/jy-transform/issues/2). -## CLI Usage +### CLI Usage -The CLI provides the `jyt` command (actually, this might require the use of options). -After the global installation you can access the `Transformer`'s command options +The CLI provides the `jyt` command which requires the use of some options. +After the global installation you can access the command options with the usual `--help` option which prints an overview about all available CLI properties: ``` $ jyt --help Usage: - jyt INPUT-FILE [OUTPUT-FILE] [OPTIONS] + jyt.js INPUT-FILE [OUTPUT-FILE] [OPTIONS] Options: -o, --origin [STRING] The origin type of INPUT-FILE: [ js | json | yaml ]. (Default is if not given, the type is tried to be inferred from the extension of source path, else it is 'yaml') -t, --target [STRING] The target type of OUTPUT-FILE: [ js | json | yaml ]. (Default is if not given, the type is tried to be inferred from the extension of destination path, else it is 'js') - -i, --indent [NUMBER] The indention for pretty-print: 1 - 8. (Default is 4) + -i, --indent [NUMBER] The indentation for pretty-print: 1 - 8. (Default is 4) -f, --force Force overwriting of existing output files on write phase. When files are not overwritten (which is default), then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in case of foo.yaml it would be foo(1).yaml. @@ -317,7 +384,7 @@ Options: -h, --help Display help and usage details ``` -### CLI Args +#### CLI Args The ARGS are more formally defined in the following table: @@ -326,9 +393,10 @@ The ARGS are more formally defined in the following table: | `INPUT-FILE` | URI | The source file path for transformation. | - | yes | | `OUTPUT-FILE` | URI | The destination file path to transform to. | When this options is omitted then the output file is stored relative to the input file (same base name but with another extension if type differs). If input and output type are the same then the file overwriting is handled depending on the `--force` value! | no | -**NOTE:** the input file has to be specified and should be _first_ argument (in fact, it can be anywhere but it must be before an out file argument)! +> **NOTE:** the _input file_ has to be specified and should be _first_ argument (in fact, it can be +> anywhere but it must be _before_ an _output file_ argument)! -### CLI Options +#### CLI Options The OPTIONS are more formally defined in the following table: @@ -336,7 +404,7 @@ The OPTIONS are more formally defined in the following table: | --- | --- | --- | --- | --- | --- | | `-o` | `--origin` | string of: [ _js_ | _json_ | _yaml_ ] | The transformation origin type. | if not given, the type is tried to be inferred from the extension of source path, else it is _yaml_ | no | | `-t` | `--target` | string of: [ _js_ | _json_ | _yaml_ ] | The transformation target type. | if not given, the type is tried to be inferred from the extension of destination path, else it is _js_ | no | -| `-i` | `--indent` | integer
[ 1 - 8 ]
| The code indention used in destination files. | 4 | no | +| `-i` | `--indent` | integer
[ 1 - 8 ]
| The code indentation used in destination files. | 2 | no | | `-f` | `--force` | n/a | Force overwriting of existing output files on write phase. When files are not overwritten (which is default), then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in case of _foo.yaml_ it would be _foo(1).yaml_. | _false_ | no | | `-m` | `--imports` | string | Define a 'module.exports[.identifier] = ' identifier (to read from JS _source_ file only, must be a valid JS identifier!) | _undefined_ | no | | `-x` | `--exports` | string | Define a 'module.exports[.identifier] = ' identifier (for usage in JS _destination_ file only, must be a valid JS identifier!) | _undefined_ | no | @@ -345,12 +413,9 @@ The OPTIONS are more formally defined in the following table: | `-v` | `--version` | n/a | Display the current version. | n/a | no | | `-h` | `--help` | n/a | Display help and usage details. | n/a | no | - -**NOTE:** an invalid indention setting (1 > `-i`, `--indent` > 8) does not raise an error but a default of 4 SPACEs is applied instead. - ### Examples -Now we know which properties can be applied on CLI, so let's assume we +Now we know which properties can be applied on CLI it's time for some examples. Let's assume we have a YAML content located in _foo.yaml_ holding this data: ```yaml @@ -362,50 +427,55 @@ Then we can transform it to a JSON content as _foo.json_ file: ```json { - "foo": "bar" + "foo": "bar" } ``` simply by using this command: -``` -$ jyt foo.yaml -t json -i 2 +```text +$ jyt foo.yaml -t json -i 4 ``` -In this example we have overwritten the standard target type (which is `js`) -and applying an indent of 2 SPACEs instead of the default (4). As default the output +In this example we have overwritten the standard _target_ type (which is `js`) +and applying an _indent_ of 4 SPACEs instead of the default (which is 2). As default the output file _foo.json_ is written relative to the input file (by omitting the `dest` option here). -**NOTE:** here you _have_ to provide the target with option `-t json` or else the -default `js` would have been applied! If the source would have been a `js` -type like +> **NOTE:** here you _have_ to provide the target with option `-t json` or else the +> default `js` would have been applied! + +If the source would have been a `js` type like in this example ``` -$ jyt foo.js -t json -i 2 +$ jyt foo.js -t json -i 4 ``` then the `js` value for `origin` is automatically inferred from file extension. Accordingly, this is also true for the `target` option. #### Example: JSON ⇒ JS + The command -``` -$ jyt foo.json -i 2 + +```text +$ jyt foo.json ``` results in _foo.js_: ```javascript -module.exports = { - foo: "bar" -} +export default {foo: 'bar'} ``` #### Example: JS ⇒ YAML + The command -``` + +```text $ jyt foo.js -t yaml ``` + results in _foo.yaml_: + ```yaml foo: bar ``` @@ -413,7 +483,8 @@ foo: bar #### Example: Transformation with Different Destination Simply specify the _output_ file with a different file name: -``` + +```text $ jyt foo.json results/foobar.yaml ``` @@ -422,11 +493,14 @@ $ jyt foo.json results/foobar.yaml As said, normally we infer from file extension to the type, but assume the source file has a file name which does not imply the type (here a JSON type in a TEXT file), then you can simply provide the `-o` option with the -correct `origin` type (of course, the `-t` option works analogous): -``` +correct `origin` type: + +```text $ jyt foo.txt foobar.yaml -o json ``` +> **NOTE:** of course, the `-t` (`--target`) option works analogous. + #### Example: Read from File with Exports Identifier It could be that a JS source `exports` several objects and you want to read @@ -435,212 +509,205 @@ from exactly the one you specify, then provide the `-m` (`--imports`) option. In this this example we have a _foo.js_ file exporting _two_ objects: ```javascript -module.exports.foo = { - foo: 'bar' +export const foo = { + foo: 'bar' }; -module.exports.bar = { - bar: 'foo' +export const bar = { + bar: 'foo' }; ``` but you want to convert only `bar` object, then call: -``` + +```text $ jyt foo.js bar.yaml -m bar ``` + to get the YAML result: + ```yaml bar: foo ``` -**NOTE:** the same applies on API level when using JS objects as `dest`: - -```javascript -var fooBar = { - foo: 'bar', - bar: 'foo' -}; - -var options = { - src: fooBar, - dest: {}, - exports: 'bar' -}; - -//...transform -``` - -The transformation will result in this in-memory object: - -```javascript -bar: { - foo: 'bar', - bar: 'foo' -} -``` -Of course, as sub-node of `options.dest`. +> **NOTE:** the same applies on API level when using JS objects as `dest`: +> +> ```javascript +> const fooBar = { +> foo: 'bar', +> bar: 'foo' +> }; +> +> const options = { +> src: fooBar, +> dest: {}, +> exports: 'bar' +> }; +> +> //...transform +> ``` +> +> The transformation will result in this in-memory object: +> +> ```javascript +> bar: { +> foo: 'bar', +> bar: 'foo' +> } +> ``` +> +> Of course, as sub-node of `options.dest`. #### Example: Write Exports Identifier for JS File Assume you want to generate a JS file with an exports string which gets an identifier. We reuse the YAML file from above: + ```yaml foo: bar ``` + using this command: + ``` $ jyt foo.yaml foobar.js -x foobar ``` + This generates the following output in JS file using `foobar` as identifier: + ```javascript -module.exports.foobar = { - foo: "bar" +export const foobar = { + foo: 'bar' } ``` -**NOTE:** the identifier must be a valid JS identifier accoding to ECMAScript 6 -(see also [Valid JavaScript variable names in ECMAScript 6](https://mathiasbynens.be/notes/javascript-identifiers-es6) -and [Generating a regular expression to match valid JavaScript identifiers](https://mathiasbynens.be/demo/javascript-identifier-regex)). +> **NOTE:** the identifier must be a valid JS identifier accoding to ECMAScript 6 +> (see also [Valid JavaScript variable names in ECMAScript 6](https://mathiasbynens.be/notes/javascript-identifiers-es6) +> and [Generating a regular expression to match valid JavaScript identifiers](https://mathiasbynens.be/demo/javascript-identifier-regex)). #### Example: Force Overwriting -**IMPORTANT NOTE:** when using this feature then any subsequent -execution which uses the same target/file name, -will overwrite the original source or target created beforehand! +> **IMPORTANT NOTE:** when using this feature then any subsequent execution which +> uses the same target/file name, can _overwrite_ the original source or target created beforehand! -By default this feature is not enabled to prevent you from accidentally +Therefore, this feature is _not_ enabled by default to prevent you from accidentally overwriting your input source or already generated targets. But let's say we want to overwrite the original source now because you want -to change the indention from 2 to 4 SPACEs, then we can do this as follows: -``` -$ jyt foo.js -f -``` -Of course, leaving out the `-f` switch creates a new file relatively to -the `origin`, named as _foo(1).js_ (note the consecutive number). Naturally, -another run of the command would result in a file called _foo(2).js_ -and so forth. - -## Origin and Target Type Inference - -The examples above have shown that we have an automatic type inference from file -extensions. This is supported as shown by the following table (from-to): +to change the indentation from 2 to 4 SPACEs, then we can do this as follows: -| File Extension | Type | -| --- | --- | -| _*.yaml_ | _yaml_ | -| _*.yml_ | _yaml_ | -| _*.js_ | _js_ | -| _*.json_ | _json_ | +```text +$ jyt foo.js -i 4 -f +``` -**NOTE:** if you have files without an extension or e.g. _*.txt_ you _have_ to -specify the origin or target type! +> **NOTE:** the other way round (i.e. leaving out the `-f` (`--force`)) switch would create a _new file_ relatively to +> the `src` _foo.js_, named as _foo(1).js_; note the consecutive number! Naturally, +> another run of the command would result in a file called _foo(2).js_ and so forth. -## API Usage +### API Usage Since the usage on CLI is a 2-step process: - 1. Read from source file to JS object ⇒ 2. Write out (maybe to other type) + 1. Read from source file to JS object ⇒ + 2. Write out (maybe to other type) -the direct API calls additionally provide the usage of a _middleware_ function -where you can alter the input JS object before it is written and therefore, which turns +the direct API calls additionally provide the optional usage of a `transform` function +where you can alter the intermediate JS object before it is written and therefore, turns this into a 3-step process: - 1. Read from source ⇒ 2. Alter the JS object ⇒ 3. Write out (maybe to other type) + 1. Read from source ⇒ + 2. Transform the JS object ⇒ + 3. Write out (maybe to other type) For more details about this and all the functions provided by this module please refer to the -[API Reference](https://github.com/deadratfink/jy-transform/wiki/API-v2). +[API Reference](https://github.com/deadratfink/jy-transform/wiki/API-v3). The `origin` and `target` type inference is also standard for the API level. -### API Properties +> **HINT:** of course, if you like you can use the `read`and `write` functionality solely besides any transformation needs. + +#### API Properties -The `Transformer` exposes the following function which takes besides an (optional) -`middleware` function the necessary `options` for the transformation: +The public `transform` function (that does not mean the optional `transform` callback here!) takes +the necessary `options` for the transformation: ```javascript -function transform(options, middleware) { - //... -} +[async] function transform(options) ``` -The `options` object has to follow this key-values table: +#### Options -| Option | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| origin | string | The origin type. | If not given, the type is tried to be inferred from the extension of source path, else it is _yaml_. | no | -| target | string | The target type. | If not given, the type is tried to be inferred from the extension of destination path, else it is _js_ | no | -| src | string | Readable | object | The source information object: `string` is used as file path, `Readable` stream provides a stringified source and `object` is used as direct JS source. | - | yes | -| dest | string | Writable | object | The destination information object: `string` is used as file path, `Writable` stream writes a stringified source and `object` is used as direct JS object for assignment. | The output file is stored relative to the input file (same base name but with another extension if type differs). If input and output type are the same then the file overwriting is handled depending on the 'force' value! | no | -| indent | number | The indention in files. | 4 | no | -| force | boolean | Force overwriting of existing output files on write phase. When files are not overwritten, then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in case of _foo.yaml_ it would be _foo(1).yaml_. | _false_ | no | -| imports | string | Define a `module.exports[.identifier] = ...` identifier (to read from JS _source_ only, must be a valid JS identifier!) | _undefined_ | no | -| exports | string | Define a `module.exports[.identifier] = ...` identifier (for usage in JS _destination_ only, must be a valid JS identifier!) | _undefined_ | no | +For a detailed description see: -**NOTE:** an invalid indention setting (1 > indent > 8) does not raise an error but a default of 4 SPACEs is applied instead. +- [Read Options](API-PUBLIC.md#readoptions--codeobjectcode) +- [Write Options](API-PUBLIC.md#writeoptions--codeobjectcode) +- [Transform Options](API-PUBLIC.md#transformoptions--codeobjectcode) -#### Example +##### Example ```javascript -var options = { - origin: 'json', - target: 'yaml', - src: 'foo.json', - dest: './foo/bar.yaml', - indent: 2 +const options = { + src: 'foo.json', + origin: 'json', // actually, not needed here, inferred from src's extension automatically! + dest: './foo/bar.yaml', + target: 'yaml', // actually, not needed here, inferred from dest's extension automatically! + indent: 4 } ``` -### Using Middleware +#### Using Transform Callback -The `middleware` is optional but if provided it must be of type `Function` and -a [Promise](http://bluebirdjs.com/docs/api-reference.html). One of the easiest -ones is the identity function +The `transform` property is optional but if provided it must be of type `Function` and could but must not be +a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)/`async`. + +- When being a Promise it has to resolve with the `data` object or reject with an `Error` based object. +- In case of normal or `async` function return `data` or throw an `Error`. + +One of the easiest ones is the identity function _f(data) → data_ -which could be expressed as -[Promise](http://bluebirdjs.com/docs/api-reference.html) function as follows: +which could be expressed as follows: ```javascript -var identity = function (data) { - return Promise.resolve(data); -} +const identity = data => data; ``` Of course, this would have no effect on the provided JS data. Actually, this one is -used internally when no middleware is provided to ensure the proper promised +used internally as default when no `transform` function is configured to ensure the proper control flow. -OK, lets go back to a more practical example, e.g. we want to alter the value of +OK, lets go back to a more practical example, e.g. we want to alter the value of a JS property before it is written to a file. Assuming we have this piece of YAML -object as input: +as input from a file called _src.yaml_: ```yaml foo: old bar ``` -Applying this [Promise](http://bluebirdjs.com/docs/api-reference.html) as middleware +Applying this `transform` callback option to the `src` content ```javascript -var middleware = function (data) { +import { transform } from 'jy-transform'; + +const options = { + src: 'src.yaml', + dest: 'result.json', + transform: async (data) => { data.foo = 'new bar'; - return Promise.resolve(data); -} + return data; + } +}; -transformer.transform(options, middleware) - .then(function (msg){ - logger.info(msg); - }) - .catch(function (err) { - logger.error(err.stack); - }); +transform(options) + .then(console.log) + .catch(console.error); ``` -will result in such JSON file: +will result in such JSON file content: ```json { - "foo": "new bar" + "foo": "new bar" } ``` @@ -654,55 +721,44 @@ Let's assume we have some Promise functions to apply. For simplicity reasons we simulate these for the moment by some functions, each adding a key-value to the given (initially empty) JS object. -**NOTE:** each of them has to resolve with the `data` object! - +> **NOTE:** each of them has to resolve with the `data` object! ```javascript -function key1(data) { - objectPath.set(data, 'key1', 'value1'); - return Promise.resolve(data); -} +const key1 = async (data) => { + data.key1 = 'value1'; + return data; +}; -function key2(data) { - objectPath.set(data, 'key2', 'value2'); - return Promise.resolve(data); -} +const key2 = async (data) => { + data.key2 = 'value2'; + return data; +}; -function key3(data) { - objectPath.set(data, 'key3', 'value3'); - return Promise.resolve(data); -} +const key3 = async (data) => { + data.key3 = 'value3'; + return data; +}; ``` These can be collected by different aggregation or composition functions of the underlying -Promise framework, e.g. using the [`Promise.all([...])`](http://bluebirdjs.com/docs/api/promise.all.html) -function. This one can collect all three functions above and ensure their proper subsequent execution: - +Promise framework, e.g. using the [`Promise.all([...])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) +function. This one can collect all three functions above and ensure their proper execution: ```javascript -var middleware = function (data) { - return Promise.all([key1(data), key2(data), key3(data)]) - .then(function(result) { - return Promise.resolve(result[result.length - 1]); - }); -}; +import { transform } from 'jy-transform'; -var logger = new Logger(); -var transformer = new Transformer(logger); -var options = { - src: {} +const options = { + src: {}, + transform: (data) => Promise.all([key1(data), key2(data), key3(data)]) + .then(result => result[result.length - 1]) }; -return transformer.transform(options, middleware) - .then(function (msg){ - logger.info(msg); - }) - .catch(function (err) { - logger.error(err.stack); - }); +transform(options) + .then(console.log) + .catch(console.error); ``` -Then the result in the `middleware` function can be retrieved from the returned +The result in the `transform` function can be retrieved from the returned array, i.e. in case of [`Promise.all([...])`](http://bluebirdjs.com/docs/api/promise.all.html) you have to pick the _last_ element which contains the "final product". @@ -710,108 +766,34 @@ From our example above it would be result in this object ```javascript { - key1: 'value1', - key2: 'value2', - key3: 'value3' + key1: 'value1', + key2: 'value2', + key3: 'value3' } ``` -which then is passed back to the transformation chain. Following this pattern +which is passed back to the transformation chain. Following this pattern you can do almost everything with the JS object, like -- deleting properties -- changing properties to other types -- validating and throwing error if not valid +- Deleting properties +- Changing properties to other types +- Validating and throwing/resolving with error if not valid - ... -Whatever you do during transformation, just keep it valid ;-) - -## Using Custom Logger - -It is usual that you use an own `logger` in your application. This module supports you by -letting you inject your logger as constructor argument: the `Reader`, `Transformer` and -`Writer` constructor will accept an (optional) logger object. - -If you do not provide one, then the default logger is `console`. - -```javascript -var logger = ...; - -var reader = new Reader(logger); -var transformer = new Transformer(logger); -var writer = new Writer(logger); -``` - -At least, the passed `logger` object _has_ to support the following functions: - -```javascript -function info(msg) -function debug(msg) -function trace(msg) -function error(err|msg) -``` -Anyway, there are some fallbacks if a level is not supported: - -- DEBUG ⇒ INFO -- TRACE ⇒ DEBUG - -# API Reference - -For more details on how to use the API, please refer to the -[API Reference](https://github.com/deadratfink/jy-transform/wiki/API-v2) -wiki which describes the full API and provides more examples. +## Contributing -# Contributing - -Pull requests and stars are always welcome. Anybody is invited to take part -into this project. For bugs and feature requests, please create an -[issue](https://github.com/deadratfink/jy-transform/issues). -See the wiki [Contributing](https://github.com/deadratfink/jy-transform/wiki/Changelog) +Pull requests and stars are always welcome. For bugs and feature requests, please create an +[issue](https://github.com/deadratfink/jy-transform/issues) or create a PR. +See the wiki [Contributing](https://github.com/deadratfink/jy-transform/wiki/Contributing) section for more details about conventions. -# Changelog - -#### v2.0.1 - -- [[#39](https://github.com/deadratfink/jy-transform/issues/39)] Maintenance release - - Update dependencies to latest - - Add travis build for Node.js v7.x and v6.x - - Docs improved/corrected - - Add target pretest in `scripts` section to `rm` _./test/tmp_ folder - -#### v2.0.0 - -- [[#33](https://github.com/deadratfink/jy-transform/issues/33)] Enhance `LogWrapper` with `TRACE` level (API) -- [[#32](https://github.com/deadratfink/jy-transform/issues/32)] Introduce input and output on CLI as ARGS instead of OPTIONS (non-backwards compatible change for CLI usage, _no_ impact on API level!) - - e.g. on CLI type in `$ jyt foo.js bar.yaml` instead of `$ jyt -s foo.js -d bar.yaml` -- [[#31](https://github.com/deadratfink/jy-transform/issues/31)] Bugfix: given `Object` source results in 'yaml' for origin (API) -- [Cleanup] Update dependencies -#### v1.0.2 +## Further information -- [[#30](https://github.com/deadratfink/jy-transform/issues/30)] Fix README and externalize API reference to wiki -- [[#29](https://github.com/deadratfink/jy-transform/issues/29)] Fix Promise warning on write process +- [Module Details](./PACKAGE.md) -#### v1.0.1 +- [Public Api Reference](./API-PUBLIC.md) -Initial public release. This covers the basic implementation and tests. The following features and fixes and part of this release: +- [Makefile Reference](./MAKE.md) -- [[#27](https://github.com/deadratfink/jy-transform/issues/27)] Export variable for JS input -- [[#22](https://github.com/deadratfink/jy-transform/issues/22)] Integrate Coveralls -- [[#21](https://github.com/deadratfink/jy-transform/issues/21)] Check and fix CodeClimate issues -- [[#20](https://github.com/deadratfink/jy-transform/issues/20)] Cleanup test dir -- [[#19](https://github.com/deadratfink/jy-transform/issues/19)] File overwrite switch (`-f`, `-force`) -- [[#18](https://github.com/deadratfink/jy-transform/issues/18)] Read and Write from other sources than file path -- [[#16](https://github.com/deadratfink/jy-transform/issues/16)] ERROR: Error: Invalid target option found while creating destination file extension -- [[#15](https://github.com/deadratfink/jy-transform/issues/15)] Measure test code coverage and add a badge -- [[#12](https://github.com/deadratfink/jy-transform/issues/12)] Create middleware collection file to use by clients and internally -- [[#11](https://github.com/deadratfink/jy-transform/issues/11)] Check all Promises for optimization possibilities -- [[#10](https://github.com/deadratfink/jy-transform/issues/10)] Integrate project with Travis -- [[#9](https://github.com/deadratfink/jy-transform/issues/9)] Resolve origin and target from file extension whenever possible -- [[#8](https://github.com/deadratfink/jy-transform/issues/8)] Enable JS reading with `require(...)` -- [[#7](https://github.com/deadratfink/jy-transform/issues/7)] YAML indent is not set to `Constants.MIN_YAML_INDENT` when indent is set to 0 -- [[#6](https://github.com/deadratfink/jy-transform/issues/6)] Finish full JSDoc for all methods -- [[#5](https://github.com/deadratfink/jy-transform/issues/5)] Write unit tests -- [[#4](https://github.com/deadratfink/jy-transform/issues/4)] Export variable for JS output -- [[#3](https://github.com/deadratfink/jy-transform/issues/3)] Promise array as middleware solved with `Promise.all([...])` diff --git a/VERSION.txt b/VERSION.txt deleted file mode 100644 index 21e8796..0000000 --- a/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -1.0.3 diff --git a/bin/create-readme.sh b/bin/create-readme.sh new file mode 100755 index 0000000..d5aac43 --- /dev/null +++ b/bin/create-readme.sh @@ -0,0 +1,142 @@ +#!/bin/bash + +api="true" +changelog="true" +license="true" +makefile="true" + +while [[ $# -gt 1 ]]; do + key="$1" + + case $key in + -a|--api) + api="$2" + shift # past argument + ;; + -c|--changelog) + changelog="$2" + shift # past argument + ;; + -l|--license) + license="$2" + ;; + -e|--env) + env="$2" + ;; + -m|--makefile) + makefile="$2" + ;; + *) + # unknown option + ;; + esac + shift +done + +############################################################################### +# PACKAGE.md +############################################################################### + +printf "Create documentation (PACKAGE.md)\n" +touch PACKAGE.md +node node_modules/.bin/package-json-to-readme --no-footer package.json > PACKAGE.md + +############################################################################### +# README.md +############################################################################### + +touch README.md +# cat readme/LOGO.md >> README.md +cat readme/BADGES.md > README.md +printf "\n\n" >> README.md +head -10 PACKAGE.md >> README.md +printf "\n\n" >> README.md + +printf "\n\n\n" >> README.md +cat readme/DOCUMENTATION.md >> README.md +printf "\n\n" >> README.md + +printf "## Further information" >> README.md +printf "\n\n" >> README.md + +printf -- "- [Module Details](./PACKAGE.md)" >> README.md +printf "\n\n" >> README.md + + +############################################################################### +# API-PUBLIC.md +############################################################################### + +if [ "$api" == "true" ]; then + printf "Create documentation (API-PUBLIC.md)\n" + touch API-PUBLIC.md + printf "\n\n\n" >> API-PUBLIC.md + node node_modules/.bin/jsdoc2md package.json index.js --no-cache --configure .jsdoc-public.json . > API-PUBLIC.md + node node_modules/.bin/doctoc API-PUBLIC.md --github --title "## TOC" --maxlevel 2 + + printf -- "- [Public Api Reference](./API-PUBLIC.md)" >> README.md + printf "\n\n" >> README.md +fi + +############################################################################### +# API-PRIVATE.md +############################################################################### + +#if [ "$api" == "true" ]; then +# printf "Create documentation (API-PRIVATE.md)\n" +# touch API-PRIVATE.md +# printf "\n\n\n" >> API-PRIVATE.md +# node node_modules/.bin/jsdoc2md --no-cache --private --configure .jsdoc.json . > API-PRIVATE.md +# node node_modules/.bin/doctoc API-PRIVATE.md --github --title "## TOC" --maxlevel 2 +# +# printf -- "- [Private Api Reference](./API-PRIVATE.md)" >> README.md +# printf "\n\n" >> README.md +#fi + +############################################################################### +# MAKE.md +############################################################################### + +MAKE_FILE=Makefile + +if [[ -f "$MAKE_FILE" ]] && [[ "$makefile" == "true" ]]; then + printf "Create documentation (MAKE.md)\n" + + printf "Target Call | Description | Dependencies\n---|---|---\n" > MAKE.md + + # find out what is the default goal (if set) + DEFAULT_TARGET=$(awk 'BEGIN {FS = "^.DEFAULT_GOAL :=|^.DEFAULT_GOAL:= |^.DEFAULT_GOAL:="} /^.DEFAULT_GOAL.*/ {printf $2}' $MAKE_FILE | sed "s/ //g") + if [ ! -z "$DEFAULT_TARGET" ]; then + printf " - Print default target: ${DEFAULT_TARGET}\n" + printf "\`$ make\` | This calls the default target \`${DEFAULT_TARGET}\`. |\n" >> MAKE.md + else + printf " - No default target found\n" + fi + + # take care of all other targets + printf " - Print all targets" + awk 'BEGIN {FS = ": |## "} /^[a-zA-Z_-]+:.*?## / {printf "\`$ make %s\` | %s | `%s`\n", $1, $3, $2}' Makefile | sed "s/| \`\`$/|/g" | sed "s/ \`$/\`/g"| sort >> MAKE.md + + printf -- "- [Makefile Reference](./MAKE.md)" >> README.md + printf "\n\n" >> README.md +fi + +############################################################################### +# Finalize README.md +############################################################################### + +if [ "$changelog" == "true" ]; then + cat CHANGELOG.md >> README.md + printf "\n\n" >> README.md +fi + +if [ "$license" == "true" ]; then + printf "## License\n\n" >> README.md + cat LICENSE.md >> README.md +fi + +############################################################################### +# Create the TOC in README.md +############################################################################### + +node node_modules/.bin/doctoc README.md --github --title "## TOC" --maxlevel 3 diff --git a/bin/test.sh b/bin/test.sh index 1a11dad..23f25c3 100644 --- a/bin/test.sh +++ b/bin/test.sh @@ -20,7 +20,8 @@ fi #git checkout --track origin/master #git checkout --track origin/development #export GIT_MERGE_AUTOEDIT=no -#git config gitflow.branch.develop development +#git config gi +# tflow.branch.develop development #git config gitflow.prefix.versiontag v #git flow init -fd #git flow release start $VERSION diff --git a/codecov.yml b/codecov.yml index ce34c4b..19eb643 100644 --- a/codecov.yml +++ b/codecov.yml @@ -67,10 +67,9 @@ coverage: # option: "X%" a static target percentage to hit branches: - master - - development - - bugfix/#* - - feature/#* - - hotfix/* + - bugfix/* + - feature/* + - refactor/* #threshold: null # allowed to drop X% and still result in a "success" commit status if_no_uploads: error # will post commit status of "error" if no coverage reports we uploaded # options: success, error, failure @@ -98,10 +97,9 @@ coverage: enabled: yes # must be yes|true to enable this status branches: - master - - development - - bugfix/#* - - feature/#* - - hotfix/* + - bugfix/* + - feature/* + - refactor/* if_no_uploads: error if_not_found: success if_ci_failed: error @@ -117,10 +115,9 @@ comment: layout: "header, diff, changes, sunburst, suggestions" branches: - master - - development - - bugfix/#* - - feature/#* - - hotfix/* + - bugfix/* + - feature/* + - refactor/* behavior: default # option: "default" posts once then update, posts new if delete # option: "once" post once then updates, if deleted do not post new # option: "new" delete old, post new diff --git a/docs/BADGES.md b/docs/BADGES.md deleted file mode 100644 index 7262b7b..0000000 --- a/docs/BADGES.md +++ /dev/null @@ -1,84 +0,0 @@ -# Stats - -## General - -| [License](https://github.com/deadratfink/jy-transform/blob/master/LICENSE.md) | [Issues](https://github.com/deadratfink/jy-transform/issues) | [Releases](https://github.com/deadratfink/jy-transform/releases) | [Tags](https://github.com/deadratfink/jy-transform/tags) | [Travis CI](https://travis-ci.org) | [Waffle](https://waffle.io/deadratfink/jy-transform) | [Code Climate](https://codeclimate.com/github/deadratfink/jy-transform) | -| --- | --- | --- | --- | --- | --- | --- | -| [![License][gh-license-image]][gh-license-url] | [![Issue Stats][gh-issues-image]][gh-issues-url] | [![Releases][gh-releases-image]][gh-releases-url] | [![Tags][gh-tags-image]][gh-tags-url] | [![Build Status][ci-image]][ci-url] | [![Waffle][waffle-image]][waffle-url] | [![Code Climate][cocl-image]][cocl-url] | - -## Branches - -| Branch | [Codecov](https://codecov.io) | [Coveralls](https://coveralls.io) | [Inch CI](http://inch-ci.org) | [David](https://david-dm.org) DM | [David](https://david-dm.org) DM (dev) | -| --- | --- | --- | --- | --- | --- | -| master | [![codecov.io][cc-image-master]][cc-url-master] | [![coveralls.io][ca-image-master]][ca-url-master] | [![inch-ci.org][inch-image-master]][inch-url-master] | [![Dependency Status][dep-image-master]][dep-url-master] | [![devDependency Status][devdep-image-master]][devdep-url-master] | -| development | [![codecov.io][cc-image-development]][cc-url-development] | [![coveralls.io][ca-image-development]][ca-url-development] | [![inch-ci.org][inch-image-development]][inch-url-development] | [![Dependency Status][dep-image-development]][dep-url-development] | [![devDependency Status][devdep-image-development]][devdep-url-development] | - -### Coverage - -| master | development | -| --- | --- | -| ![codecov.io](https://codecov.io/gh/deadratfink/jy-transform/branch/master/graphs/tree.svg) | ![codecov.io](https://codecov.io/gh/deadratfink/jy-transform/branch/development/graphs/tree.svg) | - - - - - - -## NPM - -[![NPM](https://nodei.co/npm/jy-transform.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/jy-transform/) -[![NPM](https://nodei.co/npm-dl/jy-transform.png?height=3&months=9)](https://nodei.co/npm-dl/jy-transform/) - -[gh-license-image]: https://img.shields.io/github/license/deadratfink/jy-transform.svg?style=flat-square -[gh-license-url]: https://github.com/deadratfink/jy-transform/blob/master/LICENSE.md - -[gh-issues-image]: https://img.shields.io/github/issues/deadratfink/jy-transform.svg?style=flat-square -[gh-issues-url]: https://github.com/deadratfink/jy-transform/issues - -[gh-releases-image]: https://img.shields.io/github/release/deadratfink/jy-transform.svg?style=flat-square -[gh-releases-url]: https://github.com/deadratfink/jy-transform/releases - -[gh-tags-image]: https://img.shields.io/github/tag/deadratfink/jy-transform.svg?style=flat-square -[gh-tags-url]: https://github.com/deadratfink/jy-transform/tags - - -[ci-image]: https://img.shields.io/travis/deadratfink/jy-transform.svg?style=flat-square -[ci-url]: https://travis-ci.org/deadratfink/jy-transform/branches - -[is-pull-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/pr?style=flat-square -[is-issue-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/issue?style=flat-square -[is-url]: http://issuestats.com/github/deadratfink/jy-transform - -[waffle-image]: https://badge.waffle.io/deadratfink/jy-transform.png?label=ready&title=Ready&style=flat-square -[waffle-url]: https://waffle.io/deadratfink/jy-transform - -[cocl-image]: https://img.shields.io/codeclimate/github/deadratfink/jy-transform.svg?style=flat-square -[cocl-url]: https://codeclimate.com/github/deadratfink/jy-transform - - -[cc-image-master]: https://img.shields.io/codecov/c/github/deadratfink/jy-transform/master.svg?style=flat-square -[cc-url-master]: https://codecov.io/github/deadratfink/jy-transform?branch=master -[cc-image-development]: https://img.shields.io/codecov/c/github/deadratfink/jy-transform/development.svg?style=flat-square -[cc-url-development]: https://codecov.io/github/deadratfink/jy-transform?branch=development - -[ca-image-master]: https://img.shields.io/coveralls/deadratfink/jy-transform/master.svg?style=flat-square -[ca-url-master]: https://coveralls.io/github/deadratfink/jy-transform?branch=master -[ca-image-development]: https://img.shields.io/coveralls/deadratfink/jy-transform/development.svg?style=flat-square -[ca-url-development]: https://coveralls.io/github/deadratfink/jy-transform?branch=development - - -[inch-image-master]: https://inch-ci.org/github/deadratfink/jy-transform.svg?branch=master&style=flat-square -[inch-url-master]: https://inch-ci.org/github/deadratfink/jy-transform?branch=master -[inch-image-development]: https://inch-ci.org/github/deadratfink/jy-transform.svg?branch=development&style=flat-square -[inch-url-development]: https://inch-ci.org/github/deadratfink/jy-transform?branch=development - -[dep-image-master]: https://img.shields.io/david/deadratfink/jy-transform/master.svg?style=flat-square -[dep-url-master]: https://david-dm.org/deadratfink/jy-transform/master -[dep-image-development]: https://img.shields.io/david/deadratfink/jy-transform/development.svg?style=flat-square -[dep-url-development]: https://david-dm.org/deadratfink/jy-transform/development - -[devdep-image-master]: https://img.shields.io/david/dev/deadratfink/jy-transform/master.svg?style=flat-square -[devdep-url-master]: https://david-dm.org/deadratfink/jy-transform/master#info=devDependencies -[devdep-image-development]: https://img.shields.io/david/dev/deadratfink/jy-transform/development.svg?style=flat-square -[devdep-url-development]: https://david-dm.org/deadratfink/jy-transform/development#info=devDependencies - diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md deleted file mode 100644 index 5b0e428..0000000 --- a/docs/CHANGELOG.md +++ /dev/null @@ -1,43 +0,0 @@ -#### v2.0.1 - -- [[#39](https://github.com/deadratfink/jy-transform/issues/39)] Maintenance release - - Update dependencies to latest - - Add travis build for Node.js v7.x and v6.x - - Docs improved/corrected - - Add target pretest in `scripts` section to `rm` _./test/tmp_ folder - -#### v2.0.0 - -- [[#33](https://github.com/deadratfink/jy-transform/issues/33)] Enhance `LogWrapper` with `TRACE` level (API) -- [[#32](https://github.com/deadratfink/jy-transform/issues/32)] Introduce input and output on CLI as ARGS instead of OPTIONS (non-backwards compatible change for CLI usage, _no_ impact on API level!) - - e.g. on CLI type in `$ jyt foo.js bar.yaml` instead of `$ jyt -s foo.js -d bar.yaml` -- [[#31](https://github.com/deadratfink/jy-transform/issues/31)] Bugfix: given `Object` source results in 'yaml' for origin (API) -- [Cleanup] Update dependencies - -#### v1.0.2 - -- [[#30](https://github.com/deadratfink/jy-transform/issues/30)] Fix README and externalize API reference to wiki -- [[#29](https://github.com/deadratfink/jy-transform/issues/29)] Fix Promise warning on write process - -#### v1.0.1 - -Initial public release. This covers the basic implementation and tests. The following features and fixes and part of this release: - -- [[#27](https://github.com/deadratfink/jy-transform/issues/27)] Export variable for JS input -- [[#22](https://github.com/deadratfink/jy-transform/issues/22)] Integrate Coveralls -- [[#21](https://github.com/deadratfink/jy-transform/issues/21)] Check and fix CodeClimate issues -- [[#20](https://github.com/deadratfink/jy-transform/issues/20)] Cleanup test dir -- [[#19](https://github.com/deadratfink/jy-transform/issues/19)] File overwrite switch (`-f`, `-force`) -- [[#18](https://github.com/deadratfink/jy-transform/issues/18)] Read and Write from other sources than file path -- [[#16](https://github.com/deadratfink/jy-transform/issues/16)] ERROR: Error: Invalid target option found while creating destination file extension -- [[#15](https://github.com/deadratfink/jy-transform/issues/15)] Measure test code coverage and add a badge -- [[#12](https://github.com/deadratfink/jy-transform/issues/12)] Create middleware collection file to use by clients and internally -- [[#11](https://github.com/deadratfink/jy-transform/issues/11)] Check all Promises for optimization possibilities -- [[#10](https://github.com/deadratfink/jy-transform/issues/10)] Integrate project with Travis -- [[#9](https://github.com/deadratfink/jy-transform/issues/9)] Resolve origin and target from file extension whenever possible -- [[#8](https://github.com/deadratfink/jy-transform/issues/8)] Enable JS reading with `require(...)` -- [[#7](https://github.com/deadratfink/jy-transform/issues/7)] YAML indent is not set to `Constants.MIN_YAML_INDENT` when indent is set to 0 -- [[#6](https://github.com/deadratfink/jy-transform/issues/6)] Finish full JSDoc for all methods -- [[#5](https://github.com/deadratfink/jy-transform/issues/5)] Write unit tests -- [[#4](https://github.com/deadratfink/jy-transform/issues/4)] Export variable for JS output -- [[#3](https://github.com/deadratfink/jy-transform/issues/3)] Promise array as middleware solved with `Promise.all([...])` diff --git a/docs/TOC.md b/docs/TOC.md deleted file mode 100644 index 46af769..0000000 --- a/docs/TOC.md +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/USAGE.md b/docs/USAGE.md deleted file mode 100644 index 2bbc223..0000000 --- a/docs/USAGE.md +++ /dev/null @@ -1,607 +0,0 @@ -## Motivation - -Why this module? After struggling with some huge YAML file and accidentally -occurring wrong indentions which results in an annoying failure investigation, -I decided to get rid of the YAML file and therefore, create a module which -should be aimed as the swiss army knife for transforming YAML, JS and JSON -types into each other format. - -# Usage - -The module can be used on CLI or as API (the latter is fully [Promise](http://bluebirdjs.com/docs/api-reference.html) -based). - -## Usage Types - -Since the module can be used in two different ways, use installation as follows: - -- CLI: install globally via `-g` option -- API: install locally - -Both usage types are described in more detail in the following sections. - -## Use Cases - -So, what are the typical use cases for this module? In terms of _transformation_ -these consists of different phases: - -- Reading files (`Reader`) -- Transforming JSON objects (`Transformer`) -- Apply dedicated actions on the intermediate JSON objects (`Transformer` + `Middleware`) -- Writing files (`Writer`) - -### Reading - -Reading from: - -- _*.yaml_ file -- _*.js_ file -- _*.json_ file - -Additionally, on API level to a: - -- `stream.Readable` - - Serialized JSON and YAML - - Requires `options.origin` property set - - Reads as UTF-8 -- JS `object` (actually, this means the reading phase is skipped, because object is in-memory already) - -### Transformation - -The transformation can take place into several directions: - -- YAML ⇒ JS -- YAML ⇒ JSON -- JS ⇒ YAML -- JSON ⇒ YAML -- JS ⇒ JSON -- JSON ⇒ JS -- YAML ⇒ YAML -- JSON ⇒ JSON -- JS ⇒ JS - -while: - -- [YAML](http://http://yaml.org/) = _*.yaml_, _*.yml_ -- [JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript) = _*.js_ (JS object) -- [JSON](http://json.org) = _*.json_ (JS object serialized as JSON) - -### Middleware - -Apply actions on the intermediate JS object via injected [Promise](http://bluebirdjs.com/docs/api-reference.html) -functions. This is an optional part for [transformation](#transformation) phase. - -### Writing - -Writing to: - -- _*.yaml_ file -- _*.js_ file -- _*.json_ file - -Additionally, on API level to a: - -- `stream.Writable` - - Serialized JS, JSON and YAML - - Requires `options.target` property set - - Writes UTF-8 -- JS `object` - -## Limitations - -- Since this module is build to transform from and to different type formats, any - `Function`s residing in JS type objects are _not_ supported, e.g. transforming - - ```javascript - module.exports = { - fooKey: 'foo', - fooFunction: function foo() { - //... - } - } - ``` - - to JSON would simply result in - - ```json - { - "fooKey": "foo" - } - ``` - - while transforming to YAML type would even result in an `Error`, e.g. printed - on CLI usage like this: - - ``` - ERROR: YAMLException: unacceptable kind of an object to dump [object Function] - ``` - -- Multidocument handling would be a cool feature which refers in general to YAML - and JS only, but at the moment we require that each document to transform is a - _single_ one per source (or in case of JS could be identified)! This feature is - planned and reflected in [#14](https://github.com/deadratfink/jy-transform/issues/14). -- Schema validation for input and output is another topic which is planned by - [#1](https://github.com/deadratfink/jy-transform/issues/1) and - [#2](https://github.com/deadratfink/jy-transform/issues/2). - -## CLI Usage - -The CLI provides the `jyt` command (actually, this might require the use of options). -After the global installation you can access the `Transformer`'s command options -with the usual `--help` option which prints an overview about all -available CLI properties: - -``` -$ jyt --help -Usage: - jyt INPUT-FILE [OUTPUT-FILE] [OPTIONS] - -Options: - -o, --origin [STRING] The origin type of INPUT-FILE: [ js | json | yaml ]. (Default is if not given, the type is tried to be inferred from the extension of source path, else it is 'yaml') - -t, --target [STRING] The target type of OUTPUT-FILE: [ js | json | yaml ]. (Default is if not given, the type is tried to be inferred from the extension of destination path, else it is 'js') - -i, --indent [NUMBER] The indention for pretty-print: 1 - 8. (Default is 4) - -f, --force Force overwriting of existing output files on write phase. When files are not overwritten (which is default), - then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in - case of foo.yaml it would be foo(1).yaml. - -m, --imports STRING Define a 'module.exports[.identifier] = ' identifier (to read from JS _source_ file only, must be a valid JS - identifier!). - -x, --exports STRING Define a 'module.exports[.identifier] = ' identifier (for usage in JS destination file only, must be a valid JS - identifier!). - -k, --no-color Omit color from output - --debug Show debug information - -v, --version Display the current version - -h, --help Display help and usage details -``` - -### CLI Args - -The ARGS are more formally defined in the following table: - -| Arg | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| `INPUT-FILE` | URI | The source file path for transformation. | - | yes | -| `OUTPUT-FILE` | URI | The destination file path to transform to. | When this options is omitted then the output file is stored relative to the input file (same base name but with another extension if type differs). If input and output type are the same then the file overwriting is handled depending on the `--force` value! | no | - -**NOTE:** the input file has to be specified and should be _first_ argument (in fact, it can be anywhere but it must be before an out file argument)! - -### CLI Options - -The OPTIONS are more formally defined in the following table: - -| Option (short) | Option (long) | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | --- | -| `-o` | `--origin` | string of: [ _js_ | _json_ | _yaml_ ] | The transformation origin type. | if not given, the type is tried to be inferred from the extension of source path, else it is _yaml_ | no | -| `-t` | `--target` | string of: [ _js_ | _json_ | _yaml_ ] | The transformation target type. | if not given, the type is tried to be inferred from the extension of destination path, else it is _js_ | no | -| `-i` | `--indent` | integer
[ 1 - 8 ]
| The code indention used in destination files. | 4 | no | -| `-f` | `--force` | n/a | Force overwriting of existing output files on write phase. When files are not overwritten (which is default), then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in case of _foo.yaml_ it would be _foo(1).yaml_. | _false_ | no | -| `-m` | `--imports` | string | Define a 'module.exports[.identifier] = ' identifier (to read from JS _source_ file only, must be a valid JS identifier!) | _undefined_ | no | -| `-x` | `--exports` | string | Define a 'module.exports[.identifier] = ' identifier (for usage in JS _destination_ file only, must be a valid JS identifier!) | _undefined_ | no | -| `-k` | `--no-color` | n/a | Omit color from output. | _color_ | no | -| n/a | `--debug` | n/a | Show debug information. | _false_ | no | -| `-v` | `--version` | n/a | Display the current version. | n/a | no | -| `-h` | `--help` | n/a | Display help and usage details. | n/a | no | - - -**NOTE:** an invalid indention setting (1 > `-i`, `--indent` > 8) does not raise an error but a default of 4 SPACEs is applied instead. - -### Examples - -Now we know which properties can be applied on CLI, so let's assume we -have a YAML content located in _foo.yaml_ holding this data: - -```yaml -foo: bar -``` -#### Example: YAML ⇒ JSON - -Then we can transform it to a JSON content as _foo.json_ file: - -```json -{ - "foo": "bar" -} -``` - -simply by using this command: - -``` -$ jyt foo.yaml -t json -i 2 -``` - -In this example we have overwritten the standard target type (which is `js`) -and applying an indent of 2 SPACEs instead of the default (4). As default the output -file _foo.json_ is written relative to the input file (by omitting the -`dest` option here). - -**NOTE:** here you _have_ to provide the target with option `-t json` or else the -default `js` would have been applied! If the source would have been a `js` -type like - -``` -$ jyt foo.js -t json -i 2 -``` - -then the `js` value for `origin` is automatically inferred from file extension. -Accordingly, this is also true for the `target` option. - -#### Example: JSON ⇒ JS -The command -``` -$ jyt foo.json -i 2 -``` -results in _foo.js_: -```javascript -module.exports = { - foo: "bar" -} -``` - -#### Example: JS ⇒ YAML -The command -``` -$ jyt foo.js -t yaml -``` -results in _foo.yaml_: -```yaml -foo: bar -``` - -#### Example: Transformation with Different Destination - -Simply specify the _output_ file with a different file name: -``` -$ jyt foo.json results/foobar.yaml -``` - -#### Example: Transformation with Unsupported Source File Extension - -As said, normally we infer from file extension to the type, but assume the source -file has a file name which does not imply the type (here a JSON -type in a TEXT file), then you can simply provide the `-o` option with the -correct `origin` type (of course, the `-t` option works analogous): -``` -$ jyt foo.txt foobar.yaml -o json -``` - -#### Example: Read from File with Exports Identifier - -It could be that a JS source `exports` several objects and you want to read -from exactly the one you specify, then provide the `-m` (`--imports`) option. - -In this this example we have a _foo.js_ file exporting _two_ objects: - -```javascript -module.exports.foo = { - foo: 'bar' -}; - -module.exports.bar = { - bar: 'foo' -}; -``` -but you want to convert only `bar` object, then call: -``` -$ jyt foo.js bar.yaml -m bar -``` -to get the YAML result: -```yaml -bar: foo -``` - -**NOTE:** the same applies on API level when using JS objects as `dest`: - -```javascript -var fooBar = { - foo: 'bar', - bar: 'foo' -}; - -var options = { - src: fooBar, - dest: {}, - exports: 'bar' -}; - -//...transform -``` - -The transformation will result in this in-memory object: - -```javascript -bar: { - foo: 'bar', - bar: 'foo' -} -``` -Of course, as sub-node of `options.dest`. - -#### Example: Write Exports Identifier for JS File - -Assume you want to generate a JS file with an exports string which gets an -identifier. We reuse the YAML file from above: -```yaml -foo: bar -``` -using this command: -``` -$ jyt foo.yaml foobar.js -x foobar -``` -This generates the following output in JS file using `foobar` as identifier: -```javascript -module.exports.foobar = { - foo: "bar" -} -``` - -**NOTE:** the identifier must be a valid JS identifier accoding to ECMAScript 6 -(see also [Valid JavaScript variable names in ECMAScript 6](https://mathiasbynens.be/notes/javascript-identifiers-es6) -and [Generating a regular expression to match valid JavaScript identifiers](https://mathiasbynens.be/demo/javascript-identifier-regex)). - -#### Example: Force Overwriting - -**IMPORTANT NOTE:** when using this feature then any subsequent -execution which uses the same target/file name, -will overwrite the original source or target created beforehand! - -By default this feature is not enabled to prevent you from accidentally -overwriting your input source or already generated targets. - -But let's say we want to overwrite the original source now because you want -to change the indention from 2 to 4 SPACEs, then we can do this as follows: -``` -$ jyt foo.js -f -``` -Of course, leaving out the `-f` switch creates a new file relatively to -the `origin`, named as _foo(1).js_ (note the consecutive number). Naturally, -another run of the command would result in a file called _foo(2).js_ -and so forth. - -## Origin and Target Type Inference - -The examples above have shown that we have an automatic type inference from file -extensions. This is supported as shown by the following table (from-to): - -| File Extension | Type | -| --- | --- | -| _*.yaml_ | _yaml_ | -| _*.yml_ | _yaml_ | -| _*.js_ | _js_ | -| _*.json_ | _json_ | - -**NOTE:** if you have files without an extension or e.g. _*.txt_ you _have_ to -specify the origin or target type! - -## API Usage - -Since the usage on CLI is a 2-step process: - - 1. Read from source file to JS object ⇒ 2. Write out (maybe to other type) - -the direct API calls additionally provide the usage of a _middleware_ function -where you can alter the input JS object before it is written and therefore, which turns -this into a 3-step process: - - 1. Read from source ⇒ 2. Alter the JS object ⇒ 3. Write out (maybe to other type) - -For more details about this and all the functions provided by this module please refer to the -[API Reference](https://github.com/deadratfink/jy-transform/wiki/API-v2). - -The `origin` and `target` type inference is also standard for the API level. - -### API Properties - -The `Transformer` exposes the following function which takes besides an (optional) -`middleware` function the necessary `options` for the transformation: - -```javascript -function transform(options, middleware) { - //... -} -``` - -The `options` object has to follow this key-values table: - -| Option | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| origin | string | The origin type. | If not given, the type is tried to be inferred from the extension of source path, else it is _yaml_. | no | -| target | string | The target type. | If not given, the type is tried to be inferred from the extension of destination path, else it is _js_ | no | -| src | string | Readable | object | The source information object: `string` is used as file path, `Readable` stream provides a stringified source and `object` is used as direct JS source. | - | yes | -| dest | string | Writable | object | The destination information object: `string` is used as file path, `Writable` stream writes a stringified source and `object` is used as direct JS object for assignment. | The output file is stored relative to the input file (same base name but with another extension if type differs). If input and output type are the same then the file overwriting is handled depending on the 'force' value! | no | -| indent | number | The indention in files. | 4 | no | -| force | boolean | Force overwriting of existing output files on write phase. When files are not overwritten, then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in case of _foo.yaml_ it would be _foo(1).yaml_. | _false_ | no | -| imports | string | Define a `module.exports[.identifier] = ...` identifier (to read from JS _source_ only, must be a valid JS identifier!) | _undefined_ | no | -| exports | string | Define a `module.exports[.identifier] = ...` identifier (for usage in JS _destination_ only, must be a valid JS identifier!) | _undefined_ | no | - -**NOTE:** an invalid indention setting (1 > indent > 8) does not raise an error but a default of 4 SPACEs is applied instead. - -#### Example - -```javascript -var options = { - origin: 'json', - target: 'yaml', - src: 'foo.json', - dest: './foo/bar.yaml', - indent: 2 -} -``` - -### Using Middleware - -The `middleware` is optional but if provided it must be of type `Function` and -a [Promise](http://bluebirdjs.com/docs/api-reference.html). One of the easiest -ones is the identity function - -_f(data) → data_ - -which could be expressed as -[Promise](http://bluebirdjs.com/docs/api-reference.html) function as follows: - -```javascript -var identity = function (data) { - return Promise.resolve(data); -} -``` - -Of course, this would have no effect on the provided JS data. Actually, this one is -used internally when no middleware is provided to ensure the proper promised -control flow. - -OK, lets go back to a more practical example, e.g. we want to alter the value of -JS property before it is written to a file. Assuming we have this piece of YAML -object as input: - -```yaml -foo: old bar -``` - -Applying this [Promise](http://bluebirdjs.com/docs/api-reference.html) as middleware - -```javascript -var middleware = function (data) { - data.foo = 'new bar'; - return Promise.resolve(data); -} - -transformer.transform(options, middleware) - .then(function (msg){ - logger.info(msg); - }) - .catch(function (err) { - logger.error(err.stack); - }); -``` - -will result in such JSON file: - -```json -{ - "foo": "new bar" -} -``` - -Of course, in real world scenarios you will have use cases which usually have a -higher complexity where one function might be insufficient or at least -inconvenient. but this does not raise a problem at all, because you can create -several functions to be applied in the whole transformation process by gathering -them in one function. - -Let's assume we have some Promise functions to apply. For simplicity reasons we -simulate these for the moment by some functions, each adding a key-value to the -given (initially empty) JS object. - -**NOTE:** each of them has to resolve with the `data` object! - - -```javascript -function key1(data) { - objectPath.set(data, 'key1', 'value1'); - return Promise.resolve(data); -} - -function key2(data) { - objectPath.set(data, 'key2', 'value2'); - return Promise.resolve(data); -} - -function key3(data) { - objectPath.set(data, 'key3', 'value3'); - return Promise.resolve(data); -} -``` - -These can be collected by different aggregation or composition functions of the underlying -Promise framework, e.g. using the [`Promise.all([...])`](http://bluebirdjs.com/docs/api/promise.all.html) -function. This one can collect all three functions above and ensure their proper subsequent execution: - - -```javascript -var middleware = function (data) { - return Promise.all([key1(data), key2(data), key3(data)]) - .then(function(result) { - return Promise.resolve(result[result.length - 1]); - }); -}; - -var logger = new Logger(); -var transformer = new Transformer(logger); -var options = { - src: {} -}; - -return transformer.transform(options, middleware) - .then(function (msg){ - logger.info(msg); - }) - .catch(function (err) { - logger.error(err.stack); - }); -``` - -Then the result in the `middleware` function can be retrieved from the returned -array, i.e. in case of [`Promise.all([...])`](http://bluebirdjs.com/docs/api/promise.all.html) -you have to pick the _last_ element which contains the "final product". - -From our example above it would be result in this object - -```javascript -{ - key1: 'value1', - key2: 'value2', - key3: 'value3' -} -``` - -which then is passed back to the transformation chain. Following this pattern -you can do almost everything with the JS object, like - -- deleting properties -- changing properties to other types -- validating and throwing error if not valid -- ... - -Whatever you do during transformation, just keep it valid ;-) - -## Using Custom Logger - -It is usual that you use an own `logger` in your application. This module supports you by -letting you inject your logger as constructor argument: the `Reader`, `Transformer` and -`Writer` constructor will accept an (optional) logger object. - -If you do not provide one, then the default logger is `console`. - -```javascript -var logger = ...; - -var reader = new Reader(logger); -var transformer = new Transformer(logger); -var writer = new Writer(logger); -``` - -At least, the passed `logger` object _has_ to support the following functions: - -```javascript -function info(msg) -function debug(msg) -function trace(msg) -function error(err|msg) -``` -Anyway, there are some fallbacks if a level is not supported: - -- DEBUG ⇒ INFO -- TRACE ⇒ DEBUG - -# API Reference - -For more details on how to use the API, please refer to the -[API Reference](https://github.com/deadratfink/jy-transform/wiki/API-v2) -wiki which describes the full API and provides more examples. - -# Contributing - -Pull requests and stars are always welcome. Anybody is invited to take part -into this project. For bugs and feature requests, please create an -[issue](https://github.com/deadratfink/jy-transform/issues). -See the wiki [Contributing](https://github.com/deadratfink/jy-transform/wiki/Changelog) -section for more details about conventions. - diff --git a/inch.json b/inch.json index 27b91b7..5618920 100644 --- a/inch.json +++ b/inch.json @@ -1,12 +1,13 @@ { - "files": { - "included": [ - "lib/**/*.js", - "test/test*.js", - "index.js", - "jyt" - ], - "excluded": [ - ] - } + "files": { + "included": [ + "lib/**/*.js", + "test/test*.js", + "index.js", + "jyt" + ], + "excluded": [ + "lib/**/*" + ] + } } diff --git a/index.js b/index.js index 94ae223..20f9117 100755 --- a/index.js +++ b/index.js @@ -1,7 +1 @@ -'use strict'; - -module.exports = module.exports.Transformer = require('./lib/transformer.js'); -module.exports.Reader = require('./lib/reader.js'); -module.exports.Writer = require('./lib/writer.js'); -module.exports.middleware = require('./lib/middleware.js'); -module.exports.constants = require('./lib/constants.js'); +module.exports = require('./lib/jy-transform'); diff --git a/jyt b/jyt index 029ece4..c86e3b2 100755 --- a/jyt +++ b/jyt @@ -1,111 +1,4 @@ #!/usr/bin/env node -'use strict'; - -var constants = require('./lib/constants.js'); -var cli = require('cli'); -var path = require('path'); -var Transformer = require('./lib/transformer.js'); -var transformer = new Transformer(cli); - -/////////////////////////////////////////////////////////////////////////////// -// CLI INIT -/////////////////////////////////////////////////////////////////////////////// - -/** - * How to use the CLI. - * - * @type {string} - * @private - */ -var usage = path.basename(__filename) + ' INPUT-FILE [OUTPUT-FILE] [OPTIONS]'; - -/** - * The path to package.json. - * - * @type {string} - * @private - */ -var packagePath = __dirname + '/package.json'; - -/** - * The options description for parsing the command line input, must be an object with opts defined like: - * ``` - * long_tag: [short_tag, description, value_type, default_value]; - * ``` - * @type {{origin: *[], target: *[], src: string[], dest: *[], indent: *[], force: string[], imports: string, exports: string}} - * @private - */ -var options = { - origin: [ 'o', 'The origin type of INPUT-FILE: [ ' + constants.JS + ' | ' + constants.JSON + ' | ' + constants.YAML + ' ]', 'string', constants.DEFAULT_OPTIONS.origin ], - target: [ 't', 'The target type of OUTPUT-FILE: [ ' + constants.JS + ' | ' + constants.JSON + ' | ' + constants.YAML + ' ]', 'string', constants.DEFAULT_OPTIONS.target ], - indent: [ 'i', 'The indention for pretty-print: 1 - 8', 'int', constants.DEFAULT_INDENT ], - force: [ 'f', 'Force overwriting of existing output files on write phase: when files are not overwritten (which is default), then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in case of foo.yaml it would be foo(1).yaml' ], - imports: [ 'm', 'Define a \'module.exports[.identifier] = \' identifier (to read from JS _source_ file only, must be a valid JS identifier!)', 'string', constants.DEFAULT_OPTIONS.imports ], - exports: [ 'x', 'Define a \'module.exports[.identifier] = \' identifier (for usage in JS destination file only, must be a valid JS identifier!)', 'string', constants.DEFAULT_OPTIONS.exports ] -}; - -/** - * Prints the error to console and exit with 1. - * - * @param {string|Error} err - The error to print. - * @private - */ -function error(err) { - cli.error('////////////////////////////////////////////////////////////////////////////////'); - cli.error(err); - if (err.stack) { - cli.debug(err.stack); - } - cli.error('////////////////////////////////////////////////////////////////////////////////'); - cli.getUsage(1); -} - -/** - * The main entry callback. When calling `cli.main()` this receives the `options` - * given on CLI, then does the transformation with these options and finally, it - * prints the result to the CLI. - * - * @param {array} args - The first mandatory argument is the input file - * (`args[0]`), the second (optional) argument is the - * output file (`args[0]`). - * @param {object} options - The options set on CLI. - * @private - */ -function main(args, options) { - - // read file args and set to options - - if (args.length > 0) { - cli.debug('input file: ' + args[0]); - options.src = args[0]; - } else { - error('please specify an input file as first argument!'); - } - if (args.length > 1) { - cli.debug('output file: ' + args[1]); - options.dest = args[1]; - } else { - cli.debug('output file not specified, using default'); - } - - // transform with options - - return transformer.transform(options) - .then(function (msg) { - cli.info(msg); - }) - .catch(function (err) { - error(err); - }); -} - -/** - * Init the CLI instance. - */ -cli.width = 120; -cli.setUsage(usage); -cli.setApp(packagePath); -cli.enable('version', 'status', 'timeout'); -cli.parse(options); -cli.main(main); +// eslint-disable-next-line import/no-commonjs +require('./lib/cli'); diff --git a/lib/constants.js b/lib/constants.js deleted file mode 100644 index fab466e..0000000 --- a/lib/constants.js +++ /dev/null @@ -1,297 +0,0 @@ -'use strict'; - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructs the constants. - * - * @returns {Constants} - The instance. - * @constructor - * @class Class which defines all constants usable in or with this module. - */ -function Constants() { - return this; -} - -Constants.prototype = {}; -Constants.prototype.constructor = Constants; -var constants = new Constants(); -module.exports = constants; - -/////////////////////////////////////////////////////////////////////////////// -// CONSTANTS -/////////////////////////////////////////////////////////////////////////////// - -/** - * The 'utf8' constant. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.UTF8 = 'utf8'; - -/** - * The 'yaml' type constant. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.YAML = 'yaml'; - -/** - * The 'json' type constant. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JSON = 'json'; - -/** - * The 'js' type constant. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JS = 'js'; - -/** - * The type constants assembled in an array: `[ 'yaml', 'json', 'js' ]`. - * - * @type {string[]} - * @constant - * @public - */ -Constants.prototype.TYPES = [ constants.YAML, constants.JSON, constants.JS ]; - -/** - * The default file indention (4 SPACEs). - * - * @type {number} - * @constant - * @public - */ -Constants.prototype.DEFAULT_INDENT = 4; - -/** - * The minimum file indention (0 SPACE). - * - * @type {number} - * @constant - * @public - */ -Constants.prototype.MIN_INDENT = 0; - -/** - * The maximum file indention (8 SPACEs). - * - * @type {number} - * @constant - * @public - */ -Constants.prototype.MAX_INDENT = 8; - -/** - * The default `origin` value: 'yaml'. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.DEFAULT_ORIGIN = constants.YAML; - -/** - * The default `origin` value: 'js'. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.DEFAULT_TARGET = constants.JS; - -/** - * Whether to overwrite existing file or object on output. - * - * @type {boolean} - * @constant - * @public - */ -Constants.prototype.DEFAULT_FORCE_FILE_OVERWRITE = false; - -/** - * The `origin` description value. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.ORIGIN_DESCRIPTION = 'if not given, the type is tried to be inferred from the extension of source path, else it is \'' + constants.DEFAULT_ORIGIN + '\''; - -/** - * The `target` description value. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.TARGET_DESCRIPTION = 'if not given, the type is tried to be inferred from the extension of destination path, else it is \'' + constants.DEFAULT_TARGET + '\''; - -/** - * The `dest` description value. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.DEST_DESCRIPTION = 'storing relative to input file'; - -/** - * The `src` exports identifier value to read. - * - * @type {string} - * @public - * @constant - * @example - * module.exports.foo = {...}; // here 'foo' is the identifier for an object to read from the source! - */ -Constants.prototype.DEFAULT_JS_IMPORTS_IDENTIFIER = undefined; - -/** - * The `dest` exports identifier value to write. - * - * @type {string} - * @public - * @constant - */ -Constants.prototype.DEFAULT_JS_EXPORTS_IDENTIFIER = undefined; - -/** - * The default options. - * - * @constant - * @namespace - * @property {string} origin=yaml - The default origin type. - * @property {string} target=js - The default target type. - * @property {string} dest=relative_to_input_file - The default dest description. - * @property {number} indent=4 - The default indention for files. - * @property {boolean} force=false - Whether to overwrite existing file on output. - * @property {string} imports=undefined - The exports name for reading from JS source file or objects only. - * @property {string} exports=undefined - The exports name for usage in JS file or object only. - * @see {@link ORIGIN_DESCRIPTION} - * @see {@link TARGET_DESCRIPTION} - * @see {@link DEST_DESCRIPTION} - */ -Constants.prototype.DEFAULT_OPTIONS = { - origin: constants.ORIGIN_DESCRIPTION, - target: constants.TARGET_DESCRIPTION, - dest: constants.DEST_DESCRIPTION, - indent: constants.DEFAULT_INDENT, - force: constants.DEFAULT_FORCE_FILE_OVERWRITE, - imports: constants.DEFAULT_JS_IMPORTS_IDENTIFIER, - exports: constants.DEFAULT_JS_EXPORTS_IDENTIFIER -}; - -/** - * The transformation direction YAML ⇒ JS. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.YAML_TO_JS = 'yaml2js'; - -/** - * The transformation direction YAML ⇒ JSON. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.YAML_TO_JSON = 'yaml2json'; - -/** - * The transformation direction JS ⇒ YAML. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JS_TO_YAML = 'js2yaml'; - -/** - * The transformation direction JSON ⇒ YAML. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JSON_TO_YAML = 'json2yaml'; - -/** - * The transformation direction JSON ⇒ JS. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JSON_TO_JS = 'json2js'; - -/** - * The transformation direction JS ⇒ JSON. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JS_TO_JSON = 'js2json'; - -/** - * The transformation direction YAML ⇒ YAML. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.YAML_TO_YAML = 'yaml2yaml'; - -/** - * The transformation direction JSON ⇒ JSON. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JSON_TO_JSON = 'json2json'; - -/** - * The transformation direction JS ⇒ JS. - * - * @type {string} - * @constant - * @public - */ -Constants.prototype.JS_TO_JS = 'js2js'; - -/** - * The transformation directions. - * - * @type {string[]} - * @constant - * @public - */ -Constants.prototype.TRANSFORMATIONS = [ - constants.YAML_TO_JS, - constants.YAML_TO_JSON, - constants.JS_TO_YAML, - constants.JSON_TO_YAML, - constants.JSON_TO_JS, - constants.JS_TO_JSON, - constants.YAML_TO_YAML, - constants.JSON_TO_JSON, - constants.JS_TO_JS -]; diff --git a/lib/log-wrapper.js b/lib/log-wrapper.js deleted file mode 100644 index 2bbb053..0000000 --- a/lib/log-wrapper.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict'; - -var Promise = require('bluebird'); - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructs the `LogWrapper`. - * - * @param {(logger|cli|console)} [logger=console] - Logger object. - * @returns {LogWrapper} - The instance. - * @constructor - * @example - * var logger = ...; - * var logWrapper = new LogWrapper(logger); - * @class Class which defines a `logger` wrapper usable in this module. - *

- * **NOTE:** This class is not to be intended to be called from - * outside this module! - */ -function LogWrapper(logger) { - - /** - * The logger instance. - * - * @member {(logger|cli|console)} - * @private - */ - this.logInstance = logger || console; -} - -LogWrapper.prototype = {}; -LogWrapper.prototype.constructor = LogWrapper; - -/////////////////////////////////////////////////////////////////////////////// -// LOGGER METHODS -/////////////////////////////////////////////////////////////////////////////// - -/** - * Log the options with INFO level. - * - * @param {string} msg - The message to log. - * @example - * var logger = ...; - * var logWrapper = new LogWrapper(logger); - * var msg = '...'; - * logWrapper.info(msg); - * @public - */ -LogWrapper.prototype.info = function (msg) { - this.logInstance.info(msg); -}; - -/** - * Log the options with DEBUG level (if logger supports it, else with INFO). - * - * @param {string} msg - The message to log. - * @example - * var logger = ...; - * var logWrapper = new LogWrapper(logger); - * var msg = '...'; - * logWrapper.debug(msg); - * @public - */ -LogWrapper.prototype.debug = function (msg) { - if (this.logInstance.debug && typeof this.logInstance.debug === 'function') { - this.logInstance.debug(msg); - } else { - this.info(msg); - } -}; - -/** - * Log the options with TRACE level (if logger supports it, else with DEBUG). - * - * @param {string} msg - The message to log. - * @example - * var logger = ...; - * var logWrapper = new LogWrapper(logger); - * var msg = '...'; - * logWrapper.trace(msg); - * @public - * @see {@link #debug} - */ -LogWrapper.prototype.trace = function (msg) { - if (this.logInstance.trace && typeof this.logInstance.trace === 'function') { - this.logInstance.trace(msg); - } else { - this.debug(msg); - } -}; - -/** - * Log the options with ERROR level. - * - * @param {string} msg - The message to log. - * @example - * var logger = ...; - * var logWrapper = new LogWrapper(logger); - * var msg = '...'; - * logWrapper.error(msg); - * @public - */ -LogWrapper.prototype.error = function (msg) { - this.logInstance.error(msg); -}; - -/** - * Log the options with INFO level. - * - * @param {Options} options - The properties to log with INFO. - * @returns A Promise containing the passed `options` object. - * @example - * var logger = ...; - * var logWrapper = new LogWrapper(logger); - * var options = { - * ... - * }; - * logWrapper.verboseOptions(options) - * .then(function (options) { - * ... - * }); - * @public - */ -LogWrapper.prototype.verboseOptions = function (options) { - var self = this; - return Promise.resolve() - .then(function () { - self.info('origin: ' + options.origin); - self.info('target: ' + options.target); - self.info('src: ' + options.src); - self.info('dest: ' + options.dest); - self.info('indent: ' + options.indent); - return options; - }); -}; - -exports = module.exports = LogWrapper; diff --git a/lib/middleware.js b/lib/middleware.js deleted file mode 100644 index 3fe981c..0000000 --- a/lib/middleware.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -var Promise = require('bluebird'); - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructs the `Middleware`. - * - * @returns {Middleware} - The instance. - * @constructor - * @class Class which defines middleware Promises usable in or with this module. - * @example - * var middleware = require('./lib/middleware.js'); - */ -function Middleware() { -} - -Middleware.prototype.constructor = Middleware; -module.exports = new Middleware(); - -/** - * Promise which reflects the identity of passed JSON: `f(object) → object`. - * - * @param {object} object - The JS object which is returned in Promise. - * @returns {Promise.} - A Promise resolving the passed `json` object. - * @private - */ -function identity(object) { - return Promise.resolve(object); -} - -/** - * Middleware Promise which reflects the identity of passed JSON: `f(object) → object`. - * - * @param {object} object - The object which is returned in Promise. - * @returns {Promise.} - A Promise resolving the passed `json` object. - * @example - * var middleware = require('./lib/middleware.js'); - * var identityMiddleware = middleware.identityMiddleware; - * transformer.transform(options, identityMiddleware) - * .then(function(object) { - * ... - * }): - * @public - */ -Middleware.prototype.identityMiddleware = identity; - -/** - * Ensure that the given middleware Promise is a function if set. - * If not set a new JSON 'identity' Promise is returned which simply passes - * a JSON object. - * - * @param {Function} middleware - This middleware Promise can be used to intercept - * the JSON object for altering he passed JSON, the function signature is: - * - * ``` - * function(object) - * ``` - * - * **NOTE:** the Promise has to return the processed JSON! - * @returns {Promise} - The given middleware Promise or a new JSON 'identity' middleware Promise. - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @example - * var middleware = require('./lib/middleware.js'); - * var myMiddleware = function(object) { - * //... - * }; - * transformer.transform(options, middleware.ensureMiddleware(myMiddleware)) - * .then(function(object) { - * //... - * }): - * @public - */ -Middleware.prototype.ensureMiddleware = function (middleware) { - if (middleware !== undefined && (typeof middleware !== 'function')) { - return Promise.reject(new TypeError('The provided middleware is not a Function type')); - } - if (!middleware) { - middleware = identity; - } - return middleware; -}; diff --git a/lib/options-handler.js b/lib/options-handler.js deleted file mode 100644 index 0dd221c..0000000 --- a/lib/options-handler.js +++ /dev/null @@ -1,569 +0,0 @@ -'use strict'; - -var Constants = require('./constants'); -var LogWrapper = require('./log-wrapper'); -var Promise = require('bluebird'); -var path = require('path'); -var fs = require('fs'); -var isStream = require('is-stream'); - -/** - * @typedef {object} Options - * @property {string} [origin=yaml] - The origin type. - * @property {string} [target=js] - The target type. - * @property {(string|Readable|object)} src - The source (`string` type is treated as a file path). - * @property {(string|Writable|object)} [dest] - The destination (`string` type is treated as a file path). - * @property {number} [indent=4] - The indention in files. - * @property {string} [imports=undefined] - The exports name for reading from JS source file or objects only. - * @property {string} [exports=undefined] - The exports name for usage in JS destination files only. - */ - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructs the `OptionsHandler` with an (optional) logger. - * - * @param {(logger|cli|console)} [logger=console] - Logger instance. - * @returns {OptionsHandler} The instance. - * @constructor - * @class Class which defines some useful methods to initialize and prepare the - * transformation options used in this module. - *

- * **NOTE:** This class is not to be intended to be called from - * outside this module! - * @example - * var OptionsHandler = require('./options-handler'); - * var logger = ...; - * - * var optionsHandler = new OptionsHandler(logger); - */ -function OptionsHandler(logger) { - - /** - * The logger instance. - * - * @member {(logger|cli|console)} - * @private - */ - this.logInstance = new LogWrapper(logger); - - var self = this; - - /** - * Get a file extension for a given output target. - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing a proper file extension (including '.', e.g. _'.yaml'_). - * @private - */ - function getDestFileExt(options) { - return new Promise(function (resolve, reject) { - var dot = '.'; - switch (options.target) { - case Constants.YAML: - return resolve(dot + Constants.YAML); - case Constants.JS: - return resolve(dot + Constants.JS); - case Constants.JSON: - return resolve(dot + Constants.JSON); - default: - reject(new Error('Invalid target option found while creating destination file extension: ' + options.target)); - } - }); - } - - /** - * Checks if the given type is a valid one. - * - * @param {string} type - One of `[ 'yaml' | 'json' | 'js']`. - * @returns {boolean} - `true` if is valid type, else `false`. - * @see {@link Constants#TYPES} - * @private - */ - function isValidType(type) { - return (Constants.TYPES.indexOf(type) >= 0); - } - - /** - * Promise which asserts that an origin or target is correct. If correct it - * resolved the passed `options` object, else if rejects with a `Error`. - * - * @param {Options} options - The configuration for a transformation. - * @param {string} typeName - The type name. - * @returns {Promise} - A Promise containing the passed `options` object. - * @private - */ - function assertType(options, typeName) { - return assertOptions(options, [typeName]) - .then(function (assertedOptions) { - if (isValidType(assertedOptions[typeName])) { - return assertedOptions; - } else { - return Promise.reject(new Error('Invalid ' + typeName + ' \'' + assertedOptions[typeName] + '\' found, must be one of ' + JSON.stringify(Constants.TYPES))); - } - }); - } - - /** - * A simple map for extensions to type. - * - * @type {{yml: string, yaml: string, js: string, json: string}} - * @private - */ - var typeMap = { - yml: Constants.YAML, - yaml: Constants.YAML, - js: Constants.JS, - json: Constants.JSON - }; - - /** - * Infer from path extension to a type using default value as fallback. - * - * @param {string} pathStr - The file path with or without extension. - * @param {boolean} origin - If the type is origin (true) or target (false) - * @param {string} defaultValue - The default value to use if type cannot be inferred from path. - * @returns {string} - A type value. - * @private - */ - function getTypeFromFilePath(pathStr, origin, defaultValue) { - var ext = path.extname(pathStr); - self.logInstance.debug('extension: ' + ext); - if (ext.charAt(0) === '.') { - ext = ext.substr(1); - } - - var type = typeMap[ext]; - if (!type) { - self.logInstance.debug('cannot resolve ' + (origin ? 'origin' : 'target') + ' type from file ' + pathStr + ', using default: ' + defaultValue); - type = defaultValue; - } - return type; - } - - /////////////////////////////////////////////////////////////////////////////// - // OPTIONS INIT & VALIDATION METHODS - /////////////////////////////////////////////////////////////////////////////// - - /** - * Completes the given `options` object by enriching from default values or using - * type inference if something required is "missing" (a missing `options.src` cannot - * be completed becaue this is mandatory). - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing the passed `options` object. - * @throws {Error} - If `options` or `options.src` not passed. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.completeOptions(options) - * .then(function (copiedOptions) { - * ... - * }); - * @public - */ - this.completeOptions = function (options) { - return assertOptions(options, ['src']) - .then(function (assertedOptions) { - var srcType; - var destType; - if (assertedOptions.src) { - if (typeof assertedOptions.src === 'string') { - srcType = getTypeFromFilePath(assertedOptions.src, true, Constants.YAML); - } else if (typeof assertedOptions.src === 'object') { - srcType = Constants.JS; - } // TODO: what about stream? - } - self.logInstance.debug('srcType: ' + srcType); - if (assertedOptions.dest && (typeof assertedOptions.dest === 'string')) { - self.logInstance.debug('options.dest: ' + assertedOptions.dest); - destType = getTypeFromFilePath(assertedOptions.dest, false, Constants.JS); // TODO: what about stream? - } - self.logInstance.debug('destType: ' + destType); - - assertedOptions.origin = (assertedOptions.origin && (assertedOptions.origin !== Constants.ORIGIN_DESCRIPTION)) ? assertedOptions.origin : (srcType || Constants.DEFAULT_ORIGIN); - assertedOptions.target = (assertedOptions.target && (assertedOptions.target !== Constants.TARGET_DESCRIPTION)) ? assertedOptions.target : (destType || Constants.DEFAULT_TARGET); - assertedOptions.dest = assertedOptions.dest || Constants.DEFAULT_OPTIONS.dest; - assertedOptions.indent = assertedOptions.indent || Constants.DEFAULT_OPTIONS.indent; - assertedOptions.force = assertedOptions.force || Constants.DEFAULT_OPTIONS.force; - assertedOptions.imports = assertedOptions.imports || Constants.DEFAULT_OPTIONS.imports; - assertedOptions.exports = assertedOptions.exports || Constants.DEFAULT_OPTIONS.exports; - return assertedOptions; - }); - }; - - /** - * Ensures that the given input source is valid. - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing the passed `options` object. - * @throws {Error} - If the `options.src` is not defined or the file represented by `options.src` does not exist. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.ensureSrc(options) - * .then(function (ensuredOptions) { - * ... - * }); - * @public - */ - this.ensureSrc = function (options) { - //return assertOptions(options, ['src']) - // .then(function (assertedOptions) { - // if (typeof assertedOptions.src === 'string') { - // self.logInstance.debug('options.src is to be verfied as File path: ' + assertedOptions.src); - // // check for existing source file - // try { - // var stats = fs.statSync(assertedOptions.src); - // if (stats.isDirectory()) { - // return Promise.reject(new Error('Source file (options.src) is a directory, pls specify a valid file resource!')); - // } - // } catch (err) { - // err.message = 'Error occurred while checking input file \'' + assertedOptions.src + '\' which should be existing and accessible, code: ' + err.code + ', cause: ' + err.message; - // return Promise.reject(err); - // } - // } else if (isStream.readable(assertedOptions.src)) { - // self.logInstance.debug('options.src is Readable stream'); - // if (!options.origin) { - // return Promise.reject(new Error('When options.src is a Readable stream then setting options.origin is mandatory!')); - // } - // } else { - // self.logInstance.debug('options.src is JSON Object'); - // } - // return assertedOptions; - //}); - - return assertOptions(options, ['src']) - .then(function (options) { - return assertFileSrc(options); - }) - .spread(function (checked, options) { - if (!checked) { - return assertStreamSrc(options); - } - return [checked, options]; - }) - .spread(function (checked, options) { - if (!checked) { - self.logInstance.debug('options.src is JSON Object'); - } - return Promise.resolve(options); - }) - .catch(function (err) { - self.logInstance.error('options.src is unknown or invalid object: ' + options.src); - return Promise.reject(err); - }); - }; - - function assertFileSrc(options) { - return new Promise(function (resolve, reject) { - if (typeof options.src === 'string') { - self.logInstance.debug('options.src is to be verified as File path: ' + options.src); - // check for existing source file - try { - var stats = fs.statSync(options.src); - if (stats.isDirectory()) { - return reject(new Error('Source file (options.src) is a directory, pls specify a valid file resource!')); - } - return resolve([true, options]); - } catch (err) { - err.message = 'Error occurred while checking input file \'' + options.src + '\' which should be existing and accessible, code: ' + err.code + ', cause: ' + err.message; - return reject(err); - } - } else { - return resolve([false, options]); - } - }); - } - - function assertStreamSrc(options) { - return new Promise(function (resolve, reject) { - if (isStream.readable(options.src)) { - self.logInstance.debug('options.src is Readable stream'); - if (!options.origin) { - return reject(new Error('when options.src is a Readable stream, then setting options.origin is mandatory!')); - } - return resolve([true, options]); - } else { - return resolve([false, options]); - } - }); - } - - /** - * This method ensures that destination file path is created if not set in - * options. If not, then it creates the path relative to the source file using - * its name and appending a proper extension depending on the `json` - * property of `options` (if `true` then '.js', else '.json'). - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing the passed `options` object. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.ensureDest(options) - * .then(function (ensuredOptions) { - * ... - * }); - * @public - */ - this.ensureDest = function (options) { - return assertOptions(options) - .then(function (assertedOptions) { - if (assertedOptions.dest === Constants.DEFAULT_OPTIONS.dest) { - return getDestFileExt(assertedOptions) - .then(function (destExt) { - var destDirName = path.dirname(assertedOptions.src); - var srcExt = path.extname(assertedOptions.src); - var destName = path.basename(assertedOptions.src, srcExt); - assertedOptions.dest = path.join(destDirName, destName + destExt); - self.logInstance.debug('Destination file: ' + assertedOptions.dest); - return assertedOptions; - }); - } else if (isStream.writable(assertedOptions.dest)) { - self.logInstance.debug('options.dest is Writable stream'); - if (!assertedOptions.target) { - return Promise.reject(new Error('When options.dest is a Writable stream then setting options.target is mandatory!')); - } - } else { - self.logInstance.debug('Destination file: ' + assertedOptions.dest); - } - return assertedOptions; - }); - }; - - /** - * Checks if the given origin is valid. - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing the passed `options` object. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.assertOrigin(options) - * .then(function (ensuredOptions) { - * ... - * }); - * @public - */ - this.assertOrigin = function (options) { - return assertType(options, 'origin'); - }; - - /** - * Checks if the given target is valid. - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing the passed `options` object. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.assertTarget(options) - * .then(function (ensuredOptions) { - * ... - * }); - * @public - */ - this.assertTarget = function (options) { - return assertType(options, 'target'); - }; - - /** - * Checks if a valid indention value is given and corrects values if invalid (with default value: 4 SPACEs). - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing the passed `options` object. - * @see {@link Constants#MIN_INDENT} - * @see {@link Constants#DEFAULT_INDENT} - * @see {@link Constants#MAX_INDENT} - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.ensureIndent(options) - * .then(function (ensuredOptions) { - * ... - * }); - * @public - */ - this.ensureIndent = function (options) { - return assertOptions(options) - .then(function (assertedOptions) { - self.logInstance.trace('options before ensureIndent():: ' + JSON.stringify(assertedOptions, null, 4)); - if (assertedOptions.indent === undefined) { - self.logInstance.info('Missing indention, reset to default: ' + Constants.DEFAULT_INDENT); - assertedOptions.indent = Constants.DEFAULT_INDENT; - } else if (assertedOptions.indent < Constants.MIN_INDENT) { - self.logInstance.info('Indention \'' + assertedOptions.indent + '\' is too narrow, reset to default: ' + Constants.DEFAULT_INDENT); - assertedOptions.indent = Constants.DEFAULT_INDENT; - } else if (assertedOptions.indent > Constants.MAX_INDENT) { - self.logInstance.info('Indention \'' + assertedOptions.indent + '\' is too wide, reset to default: ' + Constants.DEFAULT_INDENT); - assertedOptions.indent = Constants.DEFAULT_INDENT; - } - self.logInstance.trace('options after ensureIndent():: ' + JSON.stringify(assertedOptions, null, 4)); - return assertedOptions; - }); - }; - - /** - * Log the options with INFO level. - * - * @param {Options} options - The configuration for a transformation. The properties to log with INFO. - * @returns {Promise} - A Promise containing the passed `options` object. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.verboseOptions(options) - * .then(function (loggedOptions) { - * ... - * }); - * @private - */ - this.verboseOptions = function (options) { - return assertOptions(options) - .then(function (assertedOptions) { - return self.logInstance.verboseOptions(assertedOptions); - }); - }; -} - -OptionsHandler.prototype = {}; -OptionsHandler.prototype.constructor = OptionsHandler; -module.exports = OptionsHandler; - -/** - * This method ensures that the options object is set with all necessary and - * correct values. The method does not alter the given object, but creates - * and fills a new instance from the given values and/or default ones. - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing a new and complete `options` object. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var options = {...}; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.ensureOptions(options) - * .then(function (ensuredOptions) { - * ... - * }); - * @public - */ -OptionsHandler.prototype.ensureOptions = function (options) { - return this.completeOptions(options) - .then(this.ensureSrc) - .then(this.ensureDest) - .then(this.assertOrigin) - .then(this.assertTarget) - .then(this.ensureIndent) - .then(this.verboseOptions); -}; - -/** - * This method validates the transformation process described by the given - * options and provides the validate and enriched options and according name - * to resolve a proper function. - * - * @param {Options} options - The configuration for a transformation. - * @returns {Promise} - A Promise containing the passed `options` object and a 'transformation' string in an array. - * @example - * var OptionsHandler = require('./options-handler.js'); - * var logger = ...; - * var optionsHandler = new OptionsHandler(logger); - * - * optionsHandler.validateTransformation(options) - * .spread(function (validatedOptions, transformation) { - * ... - * )): - * @see {@link transformations} - * @public - */ -OptionsHandler.prototype.validateTransformation = function (options) { - return assertOptions(options, ['origin', 'target']) - .then(function (assertedOptions) { - return new Promise(function (resolve, reject) { - var transformation = assertedOptions.origin + '2' + assertedOptions.target; - if (Constants.TRANSFORMATIONS.indexOf(transformation) < 0) { - reject(new Error('Unsupported target type transformation \'' + assertedOptions.origin + ' -> ' + assertedOptions.target + '\' configured in options.')); - } else { - resolve([assertedOptions, transformation]); - } - }); - }); -}; - -/** - * Asserts that the given `options` and (optionally) the given properties are - * inside the options. If not, the Promise rejects with proper error message. - * - * @param {object} options - The objects which should be set. - * @param {string[]} [properties] - Properties which should exist in `options`. - * @returns {Promise} - Promise which contains the `options` as result. - * @example - * var options = {...}; - * - * assertOptions(options, ['src', 'origin']) - * .then(function (assertedOptions) { - * ... - * }); - * @public - */ -OptionsHandler.prototype.assertOptions = assertOptions; - -/** - * Asserts that the given `options` and (optionally) the given properties are - * inside the options. If not, the Promise rejects with proper error message. - * - * @param {object} options - The objects which should be set. - * @param {string[]} [properties] - Properties which should exist in `options`. - * @returns {Promise} - Promise which contains the `options` as result. - * @example - * var options = {...}; - * - * assertOptions(options, ['src', 'origin']) - * .then(function (assertedOptions) { - * ... - * }); - * @private - */ -function assertOptions(options, properties) { - return new Promise(function (resolve, reject) { - if (!options) { - return reject(new Error('missing options object!')); - } - if (properties && properties.length > 0) { - var missing = []; - properties.forEach(function (p) { - if (!options[p]) { - missing.push('options.' + p); - } - }); - if (missing.length > 0) { - return reject(new Error('missing options property(s): ' + JSON.stringify(missing) + '!')); - } - } - resolve(options); - }); -} diff --git a/lib/reader.js b/lib/reader.js deleted file mode 100644 index 1c64e2d..0000000 --- a/lib/reader.js +++ /dev/null @@ -1,322 +0,0 @@ -'use strict'; - -var Constants = require('./constants'); -var LogWrapper = require('./log-wrapper'); -var OptionsHandler = require('./options-handler'); -var Validator = require('./validator'); -var jsYaml = require('js-yaml'); -var Promise = require('bluebird'); -var fs = Promise.promisifyAll(require('fs')); -var Buffer = require('buffer').Buffer; -var path = require('path'); -var isStream = require('is-stream'); -var stringify = require('json-stringify-safe'); - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructs the `Reader` with an (optional) logger. - * - * @param {(logger|cli|console)} [logger=console] - Logger instance. - * @returns {Reader} The instance. - * @constructor - * @class This class provides utility methods usable to read YAML, JSON or JS - * from a source (file, {object} or {@link stream.Readable}) to JS memory objects. - * @example - * var Reader = require('jy-transform').Reader; - * var logger = ...; - * - * var reader = new Reader(logger); - */ -function Reader(logger) { - /** - * The logger instance. - * - * @member {(logger|cli|console)} - * @private - */ - this.logInstance = new LogWrapper(logger); - - /** - * The options handler. - * - * @type {OptionsHandler} - * @private - */ - this.optionsHandler = new OptionsHandler(logger); - - /** - * The validator. - * - * @type {Validator} - */ - this.validator = new Validator(logger); - - var self = this; - - /** - * Creates a function to read from the passed source in to the given buffer array. - * - * @param {stream.Readable} readable - The source to read from. - * @param {array} bufs - The temporary buffer array. - * @returns {Function} - The function which reads and buffers. - * @private - */ - function createReadableFunction(readable, bufs) { - return function () { - var chunk; - while (null !== (chunk = readable.read())) { - self.logInstance.trace('JSON chunk: ', chunk); - bufs.push(chunk); - } - }; - } - - /** - * Reads from a passed stream and resolves by callback. - * - * @param {stream.Readable} readable - The source to read from. - * @param {function} resolve - Callback for success case. - * @param {function} reject - Callback for Error case. - * @param {string} origin - Origin type, must be 'yaml' or 'json'/'js'. - * @private - */ - function readFromStream(readable, resolve, reject, origin) { - var bufs = []; - readable - .on('readable', createReadableFunction(readable, bufs)) - .on('error', function (err) { - reject(err); - }) - .on('end', function () { - var buffer = Buffer.concat(bufs); - try { - self.logInstance.debug(origin + ' reading from Readable'); - if (origin === Constants.JSON || origin === Constants.JS) { - resolve(JSON.parse(buffer.toString(Constants.UTF8))); - } else {// HINT: commented (see below): if (origin === Constants.YAML) { - resolve(jsYaml.safeLoad(buffer.toString(Constants.UTF8))); - } - // HINT: for the sake of test coverage it's commented, since this is a private method we have control over options.origin inside this class! - //else { - // reject(new Error('Unsupported type: ' + origin + ' to read from Readable')); - //} - } catch (err) { // probably a SyntaxError for JSON or a YAMLException - self.logInstance.error('Unexpected error: ' + err.stack); - readable.emit('error', err); // send to .on('error',... - } - }); - } - - /////////////////////////////////////////////////////////////////////////////// - // API METHODS (PUBLIC) - /////////////////////////////////////////////////////////////////////////////// - - /** - * Reads the data from a given JS or JSON source. - * - * @param {Options} options - Contains the JS/JSON source reference to read from. - * @returns {Promise} - Contains the read JS object. - * @public - * @example - * var Reader = require('jy-transform').Reader; - * var logger = ...; - * var reader = new Reader(logger); - * - * // --- from file path - * - * var options = { - * src: 'foo.js' - * }; - * - * reader.readJs(options) - * .then(function (obj){ - * logger.info(JSON.stringify(obj)); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * - * // --- from Readable - * - * options = { - * src: fs.createReadStream('foo.js') - * }; - * - * reader.readJs(options) - * .then(function (obj){ - * logger.info(JSON.stringify(obj)); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * - * // --- from object - * - * options = { - * src: { - * foo: 'bar' - * } - * }; - * - * reader.readJs(options) - * .then(function (obj){ - * logger.info(JSON.stringify(obj)); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - */ - this.readJs = function (options) { - self.logInstance.trace('OPTIONS BEFORE ASSERTING IN readJs:::' + JSON.stringify(options)); - return self.optionsHandler.assertOptions(options, ['src']) - .then(function (assertedOptions) { - return new Promise(function (resolve, reject) { - if (typeof assertedOptions.src === 'string') { - try { - var resolvedPath = path.resolve('', assertedOptions.src); - - if ((path.extname(assertedOptions.src) === '.js' || options.origin === Constants.JS) && options.imports && options.imports !== '') { - - if (!self.validator.validateIdentifier(options.imports)) { - reject(new Error('found invalid identifier for reading from exports: ' + stringify(options.imports, null, 4))); - } else { - var json = require(resolvedPath)[options.imports]; - self.logInstance.trace('LOADED JSON object (' + options.imports + '):: ' + stringify(json, null, 4)); - if (!json) { - reject(new Error('an identifier string \'' + options.imports + '\' was specified for JS object but could not find this object, pls ensure that file ' + assertedOptions.src + ' contains it.')); - } else { - resolve(json); - } - } - - } else { - resolve(require(resolvedPath)); - } - } catch (err) { // probably a SyntaxError - self.logInstance.error('Unexpected error: ' + err.stack); - reject(err); - } - } else if (isStream.readable(assertedOptions.src)) { - readFromStream(assertedOptions.src, resolve, reject, Constants.JSON); - } else if (options.imports && options.imports !== '') { - - if (!self.validator.validateIdentifier(options.imports)) { - reject(new Error('found invalid identifier for reading from object: ' + stringify(options.imports, null, 4))); - } else { - var obj = assertedOptions.src[options.imports]; - self.logInstance.trace('LOADED JSON object (' + options.imports + '):: ' + stringify(obj, null, 4)); - if (!obj) { - reject(new Error('an identifier string \'' + options.imports + '\' was specified for JS object but could not find this object, pls ensure that object source contains it.')); - } else { - resolve(obj); - } - } - } else { - resolve(assertedOptions.src); - } - }); - }); - }; - - /** - * Loads a single YAML source containing document and returns a JS object. - * - * *NOTE:* This function does not understand multi-document sources, it throws - * exception on those. - * - * @param {Options} options - Contains the YAML source reference to read from. - * @returns {Promise} - Contains the read JS object. - * @public - * @example - * var Reader = require('jy-transform').Reader; - * var logger = ...; - * var reader = new Reader(logger); - * - * // --- from file path - * - * options = { - * src: 'foo.yml' - * }; - * - * reader.readYaml(options) - * .then(function (obj){ - * logger.info(JSON.stringify(obj)); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * - * // --- from Readable - * - * options = { - * src: fs.createReadStream('foo.yml') - * }; - * - * reader.readJs(options) - * .then(function (obj){ - * logger.info(JSON.stringify(obj)); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - */ - this.readYaml = function (options) { - self.logInstance.trace('OPTIONS BEFORE ASSERTING IN readYaml::: ' + JSON.stringify(options)); - return self.optionsHandler.assertOptions(options, ['src']) - .then(function (assertedOptions) { - return new Promise(function (resolve, reject) { - if (typeof assertedOptions.src === 'string') { - // load source from YAML file - fs.readFileAsync(assertedOptions.src, Constants.UTF8) - .then(function (yaml) { - self.logInstance.debug('YAML loaded from file ' + assertedOptions.src); - try { - resolve(jsYaml.safeLoad(yaml)); - } catch (err) { // probably a YAMLException - self.logInstance.error('Unexpected error: ' + err.stack); - reject(err); - } - }); - } else if (isStream.readable(assertedOptions.src)) { - readFromStream(assertedOptions.src, resolve, reject, Constants.YAML); - } else { - resolve(assertedOptions.src); - } - }); - }); - }; - -///** -// * Parses string as single YAML source containing multiple YAML document and turns a JS objects array. -// * -// * NOTE: This function does not understand multi-document sources, it throws exception on those. -// * -// * @param src {string} The YAML source to read. -// * @returns {Promise} Containing an array holding the multiple JSON objects. -// * @public -// */ -//Reader.prototype.readYamls = function (src) { -// // load source from YAML source -// return fs.readFileAsync(src, 'utf8') -// .then(function(yaml) { -// self.logger.debug('YAML documents loaded from ' + src); // TOD: can this be shortened? -> return Promise.resolve(jsYaml.safeLoadAll(yaml)); -// return Promise.resolve().then(function () { -// var jsDocs = []; -// return jsYaml.safeLoadAll(yaml, function (doc) { // TOD this will not work in Promise environment!!! -// self.logger.trace(doc); -// jsDocs.push(doc); -// }); -// }); -// }); -//}; - -} - -Reader.prototype.constructor = Reader; -module.exports = Reader; diff --git a/lib/transformer.js b/lib/transformer.js deleted file mode 100644 index e1e5790..0000000 --- a/lib/transformer.js +++ /dev/null @@ -1,424 +0,0 @@ -'use strict'; - -var Writer = require('./writer'); -var Reader = require('./reader'); -var LogWrapper = require('./log-wrapper'); -var OptionsHandler = require('./options-handler'); -var middleware = require('./middleware'); -var path = require('path'); - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructs the `Transformer` with options and an (optional) logger. - * - * @param {(logger|cli|console)} [logger=console] - Logger instance. - * @returns {Transformer} - The instance. - * @constructor - * @class This class provides all methods usable to handle YAML, JSON and JS and - * their transformations. - * @example - * var logger = ...; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - */ -function Transformer(logger) { - - /** - * The logger instance. - * - * @member {(logger|cli|console)} - * @private - */ - this.logInstance = new LogWrapper(logger); - - /** - * The options handler. - * - * @type {OptionsHandler} - * @private - */ - this.optionsHandler = new OptionsHandler(logger); - - /** - * The internal `Writer` instance. - * - * @type {Writer} - * @private - */ - var writer = new Writer(logger); - - /** - * The internal `Reader` instance. - * - * @type {Reader} - * @private - */ - var reader = new Reader(logger); - - /** - * Ensures that basic middleware is set. - */ - var ensureMiddleware = middleware.ensureMiddleware; - - /** - * Internal delegate function to execute transformation logic (ITMO): - * - Input - * - Transform - * - Middleware - * - Write - * - * @param {Options} options - The configuration for a transformation. - * @param {function} read - The reader function. - * @param {function} [middleware] - The middleware to apply. - * @param {function} write - The writer functions. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.itmo(options, reader.readYaml, middleware, writer.writeJson); - * @private - */ - function itmo(options, read, middleware, write) { - return read(options) - .then(ensureMiddleware(middleware)) - .then(function (json) { - return write(json, options); - }); - } - - /////////////////////////////////////////////////////////////////////////////// - // TRANSFORMATION METHODS (DELEGATES) - /////////////////////////////////////////////////////////////////////////////// - - /** - * Convert YAML to JS. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to JS destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.yamlToJs(options, middleware); - * @see itmo - * @private - */ - this.yamlToJs = function (options, middleware) { - return itmo(options, reader.readYaml, middleware, writer.writeJs); - }; - - /** - * Convert YAML to JSON. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to JSON destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.yamlToJson(options, middleware); - * @see itmo - * @private - */ - this.yamlToJson = function (options, middleware) { - return itmo(options, reader.readYaml, middleware, writer.writeJson); - }; - - /** - * Convert JS to YAML. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to YAML destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.jsToYaml(options, middleware); - * @see itmo - * @private - */ - this.jsToYaml = function (options, middleware) { - return itmo(options, reader.readJs, middleware, writer.writeYaml); - }; - - /** - * Convert JSON to JS. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to JS destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.jsonToJs(options, middleware); - * @see itmo - * @private - */ - this.jsonToJs = function (options, middleware) { - return itmo(options, reader.readJs, middleware, writer.writeJs); - }; - - /** - * Convert JS to JSON. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to JSON destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.jsToJson(options, middleware); - * @see itmo - * @private - */ - this.jsToJson = function (options, middleware) { - return itmo(options, reader.readJs, middleware, writer.writeJson); - }; - - /** - * Convert YAML to YAML. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to YAML destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.yamlToYaml(options, middleware); - * @see itmo - * @private - */ - this.yamlToYaml = function (options, middleware) { - return itmo(options, reader.readYaml, middleware, writer.writeYaml); - }; - - /** - * Convert JSON to JSON. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to JSON destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (obj) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.jsonToJson(options, middleware); - * @see itmo - * @private - */ - this.jsonToJson = function (options, middleware) { - return itmo(options, reader.readJs, middleware, writer.writeJson); - }; - - /** - * Convert JS to JS. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JS object for manipulation. The function signature is: - * ``` - * function(object) - * ``` - * **NOTE:** the Promise has to return the processed JS object! - * @returns {Promise} - Containing the transformation result as message (e.g. to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to JS destination failed due to any reason. - * @example - * var logger = ...; - * var options = {...}; - * var middleware = function (json) { - * ... - * }; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * transformer.jsToJs(options, middleware); - * @see itmo - * @private - */ - this.jsToJs = function (options, middleware) { - return itmo(options, reader.readJs, middleware, writer.writeJs); - }; - - /** - * A transformation name to internal function mapping. - * - * @namespace - * @property {function} yaml2js - The transformation function for YAML -> JS. - * @property {function} yaml2json - The transformation function for YAML -> JSON. - * @property {function} yaml2yaml - The transformation function for YAML -> YAML. - * @property {function} json2yaml - The transformation function for JSON -> YAML. - * @property {function} json2js - The transformation function for JSON -> JS. - * @property {function} json2json - The transformation function for JSON -> JSON. - * @property {function} js2yaml - The transformation function for JS -> YAML. - * @property {function} js2json - The transformation function for JS -> JSON. - * @property {function} js2js - The transformation function for JS -> JS. - * @private - */ - this.transformations = { - yaml2js: this.yamlToJs, - yaml2json: this.yamlToJson, - yaml2yaml: this.yamlToYaml, - json2yaml: this.jsToYaml, - json2js: this.jsonToJs, - json2json: this.jsonToJson, - js2yaml: this.jsToYaml, - js2json: this.jsToJson, - js2js: this.jsToJs - }; -} - -/////////////////////////////////////////////////////////////////////////////// -// PROTOTYPE -/////////////////////////////////////////////////////////////////////////////// - -Transformer.prototype.constructor = Transformer; -exports = module.exports = Transformer; - -/////////////////////////////////////////////////////////////////////////////// -// API METHOD (PUBLIC) -/////////////////////////////////////////////////////////////////////////////// - -/** - * The entry method for all transformation accepting a configuration object and - * an (optional) middleware function. - * - * @param {Options} options - The configuration for a transformation. - * @param {function} [middleware] - This middleware Promise can be used to intercept - * the JSON object for altering the passed JSON, the function signature is: - * ``` - * function(json) - * ``` - * **NOTE:** the Promise has to return the processed JSON! - * @returns {Promise} - Containing the transformation result as message (e.g. - * to be logged by caller). - * @throws {TypeError} - Will throw this error when the passed `middleware` - * is not type of `Function`. - * @throws {Error} - Will throw plain error when writing to file failed due to any reason. - * @public - * @example - * var logger = ...; - * var Transformer = require('jy-transform'); - * var transformer = new Transformer(logger); - * var Promise = require('bluebird'); - * var options = {...}; - * var middleware = function (json) { - * json.myproperty = 'new value'; - * return Promise.resolve(json); - * }; - * - * transformer.transform(options, middleware) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - */ -Transformer.prototype.transform = function (options, middleware) { - var self = this; - this.logInstance.debug('transform'); - return this.optionsHandler.ensureOptions(options) - .then(this.optionsHandler.validateTransformation) - .spread(function (ensuredOptions, transformation) { - self.logInstance.info('Calling transformation: ' + transformation); - return self.transformations[transformation](ensuredOptions, middleware); - }); -}; diff --git a/lib/validator.js b/lib/validator.js deleted file mode 100644 index e1064f7..0000000 --- a/lib/validator.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -var Constants = require('./constants'); -var LogWrapper = require('./log-wrapper'); -var OptionsHandler = require('./options-handler'); -var Promise = require('bluebird'); -var stringify = require('json-stringify-safe'); -var path = require('path'); -var fs = Promise.promisifyAll(require('fs')); - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Created at [Generating a regular expression to match valid JavaScript identifiers](https://mathiasbynens.be/demo/javascript-identifier-regex). - * - * @type {RegExp} - * @private - */ -var identifierRegExpECMAScript6 = /^(?!(?:do|if|in|for|let|new|try|var|case|else|enum|eval|null|this|true|void|with|await|break|catch|class|const|false|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$)(?:[\$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D])(?:[\$0-9A-Z_a-z\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B4\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF])*$/; - -/** - * Constructs the `Validator` with an (optional) logger. - * - * @param {(logger|cli|console)} [logger=console] - Logger instance. - * @returns {Writer} The instance. - * @constructor - * @class This class validates JS identifier. - *

- * **NOTE:** this class is intended for internal use only! - * @example - * var Validator = require('./validator.js'); - * var logger = ...; - * - * var validator = new Validator(logger); - */ -function Validator(logger) { - - /** - * The logger instance. - * - * @member {(logger|cli|console)} - * @private - */ - this.logInstance = new LogWrapper(logger); - - /** - * The options handler. - * - * @type {OptionsHandler} - * @private - */ - this.optionsHandler = new OptionsHandler(logger); - - var self = this; - - /** - * This method checks if a given `identifier` is a valid ECMAScript 6 identifier. - * - * @param {string} identifier - The identifier to check. - * @returns {boolean} - `true` if valid, else `false`. - * @public - * @example - * var Validator = require('./validator.js'); - * var logger = ...; - * var validator = new Validator(logger); - * var identifier = 'foo'; - * - * logger.info('valid = ' + validator.validateIdentifier(identifier)); - */ - this.validateIdentifier = function (identifier) { - if (typeof identifier !== 'string') { - self.logInstance.debug('Invalid identifier, not a string, was type of: ' + (typeof identifier)); - return false; - } - return identifierRegExpECMAScript6.test(identifier); - }; -} - -Validator.prototype.constructor = Validator; -module.exports = Validator; diff --git a/lib/writer.js b/lib/writer.js deleted file mode 100644 index 0326e34..0000000 --- a/lib/writer.js +++ /dev/null @@ -1,509 +0,0 @@ -'use strict'; - -var Constants = require('./constants'); -var LogWrapper = require('./log-wrapper'); -var OptionsHandler = require('./options-handler'); -var Validator = require('./validator'); -var serializeJs = require('serialize-js'); -var jsYaml = require('js-yaml'); -var Promise = require('bluebird'); -var mkdirp = require('mkdirp-then'); -var jsonStringifySafe = require('json-stringify-safe'); -var path = require('path'); -var fs = Promise.promisifyAll(require('fs')); -var os = require('os'); -var isStream = require('is-stream'); - -/////////////////////////////////////////////////////////////////////////////// -// CONSTRUCTOR -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructs the `Writer` with an (optional) logger. - * - * @param {(logger|cli|console)} [logger=console] - Logger instance. - * @returns {Writer} The instance. - * @constructor - * @class This class provides utility methods usable to write JS objects - * from memory to a JSON/JS/YAML destination - * (file, object or {@link stream.Readable}). - * @example - * var Writer = require('jy-transform').Writer; - * var logger = ...; - * - * var writer = new Writer(logger); - */ -function Writer(logger) { - - /** - * The logger instance. - * - * @member {(logger|cli|console)} - * @private - */ - this.logInstance = new LogWrapper(logger); - - /** - * The options handler. - * - * @type {OptionsHandler} - * @private - */ - this.optionsHandler = new OptionsHandler(logger); - - /** - * The validator. - * - * @type {Validator} - */ - this.validator = new Validator(logger); - - var self = this; - - - /////////////////////////////////////////////////////////////////////////////// - // METHODS (PRIVATE) - /////////////////////////////////////////////////////////////////////////////// - - /** - * Creates a potential named `'module.exports[.exportsTo]'` string. - * - * @param {string} [exportsTo] - The export name. - * @returns {Promise} - Promise resolves with the exports string. - * @private - */ - function createExportsString(exportsTo) { - return new Promise(function (resolve, reject) { - var exports = 'module.exports'; - if (exportsTo && exportsTo !== '') { - if (!self.validator.validateIdentifier(exportsTo)) { - return reject(new Error('Found invalid identifier for exports: ' + exportsTo)); - } else { - exports = exports + '.' + exportsTo + ' = '; - } - } else { - exports = exports + ' = '; - } - resolve(exports); - }); - } - - /** - * Serialize a JS object to string. - * - * @param {object} object - The JS Object to serialize. - * @param {number} indent - The indention. - * @param {string} [exportsTo] - Name for export (*IMPORTANT:* must be a valid ES6 identifier). - * @returns {Promise} - Promise resolve with the serialized JS object. - * @private - * @todo [[#35](https://github.com/deadratfink/jy-transform/issues/35)] Add `'use strict';` in JS output file (-> `'\'use strict\';' + os.EOL + os.EOL + ...`)? - */ - function serializeJsToString(object, indent, exportsTo) { - return createExportsString(exportsTo) - .then(function(exportsStr) { - return exportsStr + serializeJs.serialize(object, {indent: indent}) + ';' + os.EOL; - }); - } - - /** - * Serialize a JS object to JSON string. - * - * @param {object} object - Object to serialize. - * @param {number} indent - Indention. - * @returns {string} - The serialized JSON. - * @private - */ - function serializeJsToJsonString(object, indent) { - return jsonStringifySafe(object, null, indent) + os.EOL; - } - - /** - * Turns the destination file name into a name containing a consecutive - * number if it exists. It iterates over the files until it finds a file - * name which does not exist. - * - * @param {string} dest - The destination file. - * @returns {string} - A consecutive file name or the original one if - * `dest` file does not exist. - * @private - */ - function getConsecutiveDestName(dest) { - var tmpDest = dest; - var i = 1; - var destDirName = path.dirname(tmpDest); - var ext = path.extname(tmpDest); - var basename = path.basename(tmpDest, ext); - while (fs.existsSync(tmpDest)) { - tmpDest = path.join(destDirName, basename + '(' + i++ + ')' + ext); - } - return tmpDest; - } - - /** - * Writes a serialized object to file. - * - * @param {string} object - The object to write into file. - * @param {string} dest - The file destination path. - * @param {string} target - The target type, one of [ 'yaml' | 'json' | 'js' ]. - * @param {function} resolve - The Promise `resolve` callback. - * @param {function} reject - The Promise `reject` callback. - * @param {boolean} [forceOverwrite] - Force overwriting the destination file if `true`. - * @see {@link Constants#YAML} - * @see {@link Constants#JSON} - * @see {@link Constants#JS} - * @returns {Promise} - Containing the write success message to handle by caller (e.g. for logging). - * @throws {Error} - If serialized JSON file could not be written due to any reason. - * @private - */ - function writeToFile(object, dest, target, resolve, reject, forceOverwrite) { - - /** - * Ensures that all dirs exists for dest and writes the file. - * - * @private - */ - function mkdirAndWrite() { - var destDir = path.dirname(dest); - self.logInstance.debug('Destination dir: ' + destDir); - mkdirp(destDir) - .then(function () { - self.logInstance.debug('Destination dir ' + destDir + ' successfully written'); - if (forceOverwrite === undefined || forceOverwrite === false) { - dest = getConsecutiveDestName(dest); - self.logInstance.info('Setting was: do not overwrite, using destination ' + dest + '.'); - } - return fs.writeFileAsync(dest, object, Constants.UTF8); - }) - .then(function () { - resolve('Writing \'' + target + '\' file \'' + dest + '\' successful.'); - }) - .catch(function (err) { - err.message = 'Could not write \'' + target + '\' file \'' + dest + '\', cause: ' + err.message; - reject(err); - }); - } - - return fs.statAsync(dest) - .then(function (stats) { - if (stats.isDirectory()) { - reject(new Error('Destination file is a directory, pls specify a valid file resource!')); - } else { - // file exists - mkdirAndWrite(); - } - }) - .catch(function (err) { - // ignore error (because file could possibly not exist at this point of time) - mkdirAndWrite(); - }); - } - - /** - * Writes a string serialized data object to a stream. - * - * @param {string} object - The data to write into stream. - * @param {string} dest - The stream destination. - * @param {string} target - The target type, one of [ 'yaml' | 'json' | 'js' ]. - * @param {function} resolve - The Promise `resolve` callback. - * @param {function} reject - The Promise `reject` callback. - * @see {@link Constants#YAML} - * @see {@link Constants#JSON} - * @see {@link Constants#JS} - * @returns {Promise} - Containing the write success message to handle by caller (e.g. for logging). - * @throws {Error} - If serialized JS object could not be written due to any reason. - * @private - */ - function writeToStream(object, dest, target, resolve, reject) { - dest.on('error', function (err) { - reject(err); - }) - .on('finish', function () { - resolve('Writing ' + target + ' to stream successful.'); - }); - - // write stringified data - dest.write(object); - dest.end(); - } - - /////////////////////////////////////////////////////////////////////////////// - // API METHODS (PUBLIC) - /////////////////////////////////////////////////////////////////////////////// - - /** - * Writes a JS object to a YAML destination. - * - * @param {object} object - The JS object to write into YAML destination. - * @param {Options} options - The write destination and indention. - * @see {@link Constants#MIN_INDENT} - * @see {@link Constants#DEFAULT_INDENT} - * @see {@link Constants#MAX_INDENT} - * @returns {Promise} - Containing the write success message to handle by caller (e.g. for logging). - * @throws {Error} - If YAML destination could not be written due to any reason. - * @public - * @example - * var Writer = require('jy-transform').Writer; - * var logger = ...; - * var writer = new Writer(logger); - * - * // ---- write obj to file - * - * var obj = {...}, - * var options = { - * dest: 'result.yml', - * indent: 2 - * } - * - * writer.writeYaml(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * - * // ---- write obj to Writable - * - * options = { - * dest: fs.createWriteStream('result.yml'), - * indent: 4 - * } - * - * writer.writeYaml(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - */ - this.writeYaml = function (object, options) { - return self.optionsHandler.ensureIndent(options) - .then(function (ensuredOptions) { - return new Promise(function (resolve, reject) { - // complain about missing destination - if (!ensuredOptions.dest) { - reject(new Error('Missing options.dest, pls specify existing destination resource!')); - } else { - var dest = ensuredOptions.dest; - var indent = ensuredOptions.indent; - - var yaml; - try { - yaml = jsYaml.safeDump(object, {indent: indent, noRefs: true}); - } catch (err) { - err.message = 'Could not write YAML file \'' + dest + '\', cause: ' + err.message; - return reject(err); - } - - if (typeof dest === 'string') { // file - writeToFile(yaml, dest, Constants.YAML, resolve, reject, ensuredOptions.force); - } else if (isStream.writable(dest)) { // stream - writeToStream(yaml, dest, Constants.YAML, resolve, reject); - } else { // object - ensuredOptions.dest = yaml; - resolve('Writing YAML to options.dest successful.'); - } - } - }); - }); - }; - - /** - * Writes a JS object to a JSON destination. - * - * @param {object} object - The JS object to write into JSON destination. - * @param {Options} options - The write destination and indention. - * @see {@link Constants#MIN_INDENT} - * @see {@link Constants#DEFAULT_INDENT} - * @see {@link Constants#MAX_INDENT} - * @returns {Promise} - Containing the write success message to handle by caller (e.g. for logging). - * @public - * @example - * var Writer = require('jy-transform').Writer; - * var logger = ...; - * var writer = new Writer(logger); - * - * // ---- write obj to file - * - * var obj = {...}; - * var options = { - * dest: 'result.json', - * indent: 2 - * } - * - * writer.writeJson(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * - * // ---- write obj to Writable - * - * options = { - * dest: fs.createWriteStream('result.json'), - * indent: 4 - * } - * - * writer.writeJson(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * // ---- write obj to object - * - * options = { - * dest: {}, - * indent: 4 - * } - * - * writer.writeJson(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - */ - this.writeJson = function (object, options) { - return self.optionsHandler.ensureIndent(options) - .then(function (ensuredOptions) { - return new Promise(function (resolve, reject) { - // complain about missing destination - if (!ensuredOptions.dest) { - reject(new Error('Missing options.dest, pls specify existing destination resource!')); - } else { - var dest = ensuredOptions.dest; - var indent = ensuredOptions.indent; - if (typeof dest === 'string') { // file - writeToFile(serializeJsToJsonString(object, indent), dest, Constants.JSON, resolve, reject, ensuredOptions.force); - } else if (isStream.writable(dest)) { // stream - writeToStream(serializeJsToJsonString(object, indent), dest, Constants.JSON, resolve, reject); - } else { // object - ensuredOptions.dest = serializeJsToJsonString(object, indent); - resolve('Writing JSON to options.dest successful.'); - } - } - }); - }); - }; - - /** - * Writes a JS object to a JS destination. The object is prefixed by `module.exports = `. - * - * @param {object} object - The JSON to write into JS destination. - * @param {Options} options - The write destination and indention. - * @see {@link Constants#MIN_INDENT} - * @see {@link Constants#DEFAULT_INDENT} - * @see {@link Constants#MAX_INDENT} - * @returns {Promise} - Containing the write success message to handle by caller (e.g. for logging). - * @public - * @example - * var Writer = require('jy-transform').Writer; - * var logger = ...; - * var writer = new Writer(logger); - * - * // ---- write obj to file - * - * var obj = {...}; - * var options = { - * dest: 'result.js', - * indent: 2 - * } - * - * writer.writeJs(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * - * // ---- write obj to Writable - * - * options = { - * dest: fs.createWriteStream('result.json'), - * indent: 4 - * } - * - * writer.writeJs(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - * - * - * // ---- write obj to object - * - * options = { - * dest: {}, - * indent: 2 - * } - * - * writer.writeJs(obj, options) - * .then(function (msg){ - * logger.info(msg); - * }) - * .catch(function (err) { - * logger.error(err.stack); - * }); - */ - this.writeJs = function (object, options) { - return self.optionsHandler.ensureIndent(options) - .then(function (ensuredOptions) { - return new Promise(function (resolve, reject) { - // complain about missing destination - if (!ensuredOptions.dest) { - reject(new Error('Missing options.dest, pls specify existing destination resource!')); - } else { - var dest = ensuredOptions.dest; - var indent = ensuredOptions.indent; - if (typeof dest === 'string') { // file - serializeJsToString(object, indent, ensuredOptions.exports) - .then(function (data) { - return writeToFile(data, dest, Constants.JS, resolve, reject, ensuredOptions.force); - }) - .catch(function (err) { - reject(err); - }); - } else if (isStream.writable(dest)) { // stream - serializeJsToString(object, indent, ensuredOptions.exports) - .then(function (data) { - return writeToStream(data, dest, Constants.JS, resolve, reject); - }) - .catch(function (err) { - reject(err); - }); - } else { // object - var msg; - if (ensuredOptions.exports && ensuredOptions.exports !== '') { - if (!self.validator.validateIdentifier(ensuredOptions.exports)) { - reject(new Error('Found invalid identifier for exports: ' + ensuredOptions.exports)); - } else { - ensuredOptions.dest[ensuredOptions.exports] = object; - msg = 'Writing JS to options.dest.' + ensuredOptions.exports + ' successful.'; - } - } else { - ensuredOptions.dest = object; - msg = 'Writing JS to options.dest successful.'; - } - resolve(msg); - } - } - }); - }); - }; -} - -Writer.prototype.constructor = Writer; -module.exports = Writer; diff --git a/package.json b/package.json index 0f8787e..721f6c6 100644 --- a/package.json +++ b/package.json @@ -1,100 +1,112 @@ { - "name": "jy-transform", - "description": "This project aims to read, write and transform YAML, JS or JSON objects into each other using CLI or API, while the source and destination resources can be files on CLI and additionally, objects or streams on API level.", - "version": "2.0.1", - "homepage": "https://github.com/deadratfink/jy-transform", - "author": { - "name": "Jens Krefeldt", - "email": "j.krefeldt@gmail.com", - "url": "https://github.com/deadratfink" - }, - "contributors": [ - ], - "license": "SEE LICENSE IN [LICENSE.md](https://github.com/deadratfink/jy-transform/blob/master/LICENSE.md)", - "repository": { - "type": "git", - "url": "https://github.com/deadratfink/jy-transform.git" - }, - "publishConfig": { - "registry": "https://registry.npmjs.org/" - }, - "bugs": "https://github.com/deadratfink/jy-transform/issues", - "private": false, - "config": { - "test": { - "mocha": { - "unit": { - "reporter": "spec" - } - } - } - }, - "scripts": { - "docs": "cat docs/LOGO.md > README.md && cat docs/BADGES.md >> README.md && cat docs/TOC.md >> README.md && package-json-to-readme --no-footer ./package.json >> README.md && cat docs/USAGE.md >> README.md && echo '\n# Changelog\n' >> README.md && cat docs/CHANGELOG.md >> README.md && doctoc README.md --github --title '# TOC' --maxlevel 2", - "wiki": "jsdoc2md ./jyt lib/*.js index.js > docs/API.md && doctoc docs/API.md --github --title '### TOC' --maxlevel 2 && cat docs/API.md > '../jy-transform.wiki/API-v2.md' && cat docs/CONTRIBUTING.md > ../jy-transform.wiki/Contributing.md && cat docs/CHANGELOG.md > ../jy-transform.wiki/Changelog.md && doctoc ../jy-transform.wiki/Changelog.md --github --title '### TOC' --maxlevel 3", - "pretest": "rm -Rf test/tmp", - "test": "istanbul cover _mocha --report lcovonly -- -R $npm_package_config_test_mocha_unit_reporter ./test/test*.js" - }, - "engines": { - "node": ">=0.10.0" - }, - "dependencies": { - "bluebird": "^3.4.6", - "cli": "^1.0.1", - "is-stream": "^1.1.0", - "js-yaml": "^3.6.1", - "json-stringify-safe": "^5.0.1", - "mkdirp-then": "^1.2.0", - "serialize-js": "^1.1.0" - }, - "devDependencies": { - "codeclimate-test-reporter": "^0.5.0", - "codecov": "^2.3.0", - "coveralls": "^2.11.14", - "doctoc": "^1.0.0", - "fs-extra": "^4.0.1", - "istanbul": "^0.4.5", - "jsdoc-parse": "^3.0.0", - "jsdoc-to-markdown": "^3.0.0", - "mocha": "^3.1.2", - "mocha-lcov-reporter": "^1.2.0", - "object-path": "^0.11.2", - "package-json-to-readme": "^2.0.0", - "winston": "^2.3.0" - }, - "preferGlobal": true, - "bin": { - "jyt": "./jyt" - }, - "main": "./index.js", - "keywords": [ - "api", - "cli", - "jy", - "jyt", - "jy-transform", - "transform", - "convert", - "javascript", - "js", - "json", - "yaml", - "yaml2js", - "yaml-2-js", - "yaml2json", - "yaml-2-json", - "js2yaml", - "js-2-yaml", - "json2yaml", - "json-2-yaml", - "yamltojs", - "yaml-to-js", - "yamltojson", - "yaml-to-json", - "jstoyaml", - "js-to-yaml", - "jsontoyaml", - "json-to-yaml", - "promise" - ] + "name": "jy-transform", + "description": "This project aims to read, write and transform YAML, JS or JSON objects into each other using CLI or API, while the source and destination resources can be files on CLI and additionally, objects or streams on API level.", + "version": "3.0.0", + "homepage": "https://github.com/deadratfink/jy-transform", + "author": { + "name": "Jens Krefeldt", + "email": "j.krefeldt@gmail.com", + "url": "https://github.com/deadratfink" + }, + "contributors": [], + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/deadratfink/jy-transform.git" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "bugs": "https://github.com/deadratfink/jy-transform/issues", + "private": false, + "scripts": { + "build": "NODE_ENV=production babel src --out-dir lib --copy-files --source-maps", + "readme": "bin/create-readme.sh -a true -c false -m true -l false", + "wiki": "jsdoc2md ./jyt lib/*.js index.js > docs/API.md && doctoc docs/API.md --github --title '### TOC' --maxlevel 2 && cat docs/API.md > '../jy-transform.wiki/API-v2.md' && cat docs/CONTRIBUTING.md > ../jy-transform.wiki/Contributing.md && cat docs/CHANGELOG.md > ../jy-transform.wiki/Changelog.md && doctoc ../jy-transform.wiki/Changelog.md --github --title '### TOC' --maxlevel 3", + "pretest": "mkdir -pv test/tmp", + "test": "jest --forceExit --expand --no-cache --coverage --config=./.jestrc.js", + "eslint": "eslint . --ext .json --ext .js", + "nsp": "nsp check", + "inch": "inchjs suggest && inchjs list --all && inchjs stats" + }, + "engines": { + "node": ">=6.0.0" + }, + "dependencies": { + "babel-runtime": "^6.26.0", + "cli": "~1.0.1", + "is-stream": "~1.1.0", + "joi": "~12.0.0", + "js-yaml": " ~3.10.0", + "json-stringify-safe": "~5.0.1", + "mkdirp-then": "~1.2.0", + "promisify-es6": "~1.0.3", + "serialize-js": "deadratfink/serialize-js#feature/single_quote_only" + }, + "devDependencies": { + "babel-cli": "~6.26.0", + "babel-eslint": " ~8.0.2", + "babel-plugin-transform-flow-strip-types": "~6.22.0", + "babel-plugin-transform-runtime": "~6.23.0", + "babel-preset-env": "~1.6.1", + "chalk": "~2.3.0", + "codacy-coverage": "~2.0.2", + "codeclimate-test-reporter": "~0.5.0", + "codecov": " ~3.0.0", + "coveralls": " ~3.0.0", + "cwd": "~0.10.0", + "doctoc": " ~1.3.0", + "eslint": "~4.11.0", + "eslint-config-airbnb-base": "~12.1.0", + "eslint-plugin-filenames": "1.2.0", + "eslint-plugin-import": " ~2.8.0", + "eslint-plugin-jest": " ~21.3.2", + "eslint-plugin-jest-async": " ~1.0.3", + "eslint-plugin-jsdoc": " ~3.2.0", + "eslint-plugin-json": "^1.2.0", + "fs-extra": "~4.0.1", + "inchjs": "~0.4.1", + "jest": "~21.2.1", + "jsdoc-babel": " ~0.3.0", + "jsdoc-parse": "~3.0.0", + "jsdoc-to-markdown": "~3.0.2", + "nsp": "^3.2.1", + "package-json-to-readme": "~2.0.0", + "winston": "^2.4.0" + }, + "preferGlobal": true, + "bin": { + "jyt": "./src/cli.js" + }, + "main": "./index.js", + "keywords": [ + "api", + "cli", + "jy", + "jyt", + "jy-transform", + "transform", + "convert", + "javascript", + "js", + "json", + "yaml", + "yaml2js", + "yaml-2-js", + "yaml2json", + "yaml-2-json", + "js2yaml", + "js-2-yaml", + "json2yaml", + "json-2-yaml", + "yamltojs", + "yaml-to-js", + "yamltojson", + "yaml-to-json", + "jstoyaml", + "js-to-yaml", + "jsontoyaml", + "json-to-yaml", + "promise" + ] } diff --git a/readme/BADGES.md b/readme/BADGES.md new file mode 100644 index 0000000..5610584 --- /dev/null +++ b/readme/BADGES.md @@ -0,0 +1,90 @@ +[![Node version][node-version-image]][node-version-url] +[![License][gh-license-image]][gh-license-url] +[![Issue Stats][gh-issues-image]][gh-issues-url] +[![Releases][gh-releases-image]][gh-releases-url] +[![Tags][gh-tags-image]][gh-tags-url] +[![Build Status][ci-image]][ci-url] +[![Waffle][waffle-ready-image]][waffle-url] +[![Waffle][waffle-waffle-in-progress-image]][waffle-url] +[![Code Climate][cocl-image]][cocl-url] +[![codecov.io][cc-image-master]][cc-url-master] +[![coveralls.io][ca-image-master]][ca-url-master] +[![inch-ci.org][inch-image-master]][inch-url-master] +[![bitHound Overall Score](https://www.bithound.io/github/deadratfink/jy-transform/badges/score.svg)](https://www.bithound.io/github/deadratfink/jy-transform) +[![bitHound Code][bithound-code-image]][bithound-url] +[![bitHound Dependencies][bitHound-dependencies-image]][bitHound-dependencies] +[![bitHound Dev Dependencies][bitHound-dev-dependencies-image]][bitHound-dependencies] +[![Codacy Badge](https://img.shields.io/codacy/grade/c2ebaac0f9874062ba468ff6bd7edc4e.svg?style=flat)](https://www.codacy.com/app/deadratfink/jy-transform?utm_source=github.com&utm_medium=referral&utm_content=deadratfink/jy-transform&utm_campaign=Badge_Grade) +[![Codacy Badge](https://img.shields.io/codacy/coverage/c2ebaac0f9874062ba468ff6bd7edc4e.svg?style=flat)](https://www.codacy.com/app/deadratfink/jy-transform?utm_source=github.com&utm_medium=referral&utm_content=deadratfink/jy-transform&utm_campaign=Badge_Coverage) +[![Greenkeeper badge](https://badges.greenkeeper.io/deadratfink/jy-transform.svg?style=flat)](https://greenkeeper.io/) +[![NSP Status][nsp-image-master]][nsp-url-master] +[![HitCount](http://hits.dwyl.io/deadratfink/jy-transform.svg?style=flat)](http://hits.dwyl.io/deadratfink/jy-transform) + + + + +[![NPM][npm-image]][npm-url] + + +[gh-license-image]: https://img.shields.io/github/license/deadratfink/jy-transform.svg?style=flat +[gh-license-url]: https://github.com/deadratfink/jy-transform/blob/master/LICENSE.md + +[gh-issues-image]: https://img.shields.io/github/issues/deadratfink/jy-transform.svg?style=flat +[gh-issues-url]: https://github.com/deadratfink/jy-transform/issues + +[gh-releases-image]: https://img.shields.io/github/release/deadratfink/jy-transform.svg?style=flat +[gh-releases-url]: https://github.com/deadratfink/jy-transform/releases + +[gh-tags-image]: https://img.shields.io/github/tag/deadratfink/jy-transform.svg?style=flat +[gh-tags-url]: https://github.com/deadratfink/jy-transform/tags + + +[ci-image]: https://img.shields.io/travis/deadratfink/jy-transform.svg?style=flat +[ci-url]: https://travis-ci.org/deadratfink/jy-transform/branches + +[is-pull-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/pr?style=flat +[is-issue-image]: http://issuestats.com/github/deadratfink/jy-transform/badge/issue?style=flat +[is-url]: http://issuestats.com/github/deadratfink/jy-transform + +[waffle-ready-image]: https://img.shields.io/waffle/label/deadratfink/jy-transform.svg?label=ready&title=Waffle%20Ready&style=flat +[waffle-waffle-in-progress-image]: https://img.shields.io/waffle/label/deadratfink/jy-transform.svg?label=in%20progress&title=Waffle%20In%20Progress&style=flat +[waffle-url]: https://waffle.io/deadratfink/jy-transform + +[cocl-image]: https://img.shields.io/codeclimate/github/deadratfink/jy-transform.svg?style=flat +[cocl-url]: https://codeclimate.com/github/deadratfink/jy-transform + + +[cc-image-master]: https://img.shields.io/codecov/c/github/deadratfink/jy-transform/master.svg?style=flat +[cc-url-master]: https://codecov.io/github/deadratfink/jy-transform?branch=master + +[ca-image-master]: https://img.shields.io/coveralls/deadratfink/jy-transform/master.svg?style=flat +[ca-url-master]: https://coveralls.io/github/deadratfink/jy-transform?branch=master + + +[inch-image-master]: https://inch-ci.org/github/deadratfink/jy-transform.svg?branch=master&style=flat +[inch-url-master]: https://inch-ci.org/github/deadratfink/jy-transform?branch=master + +[dep-image-master]: https://img.shields.io/david/deadratfink/jy-transform/master.svg?style=flat +[dep-url-master]: https://david-dm.org/deadratfink/jy-transform/master + +[devdep-image-master]: https://img.shields.io/david/dev/deadratfink/jy-transform/master.svg?style=flat +[devdep-url-master]: https://david-dm.org/deadratfink/jy-transform/master#info=devDependencies + +[nsp-image-master]: https://nodesecurity.io/orgs/deadratfink/projects/7ac99a62-a8c4-4321-8d57-8a5e542f04f0/badge?style=flat +[nsp-url-master]: https://nodesecurity.io/orgs/deadratfink/projects/7ac99a62-a8c4-4321-8d57-8a5e542f04f0 + +[node-version-image]: https://img.shields.io/node/v/jy-transform.svg?style=flat +[node-version-url]: http://nodejs.org/download/ + +[npm-image]: https://nodei.co/npm/jy-transform.png?downloads=true&downloadRank=true&stars=true +[npm-url]: https://nodei.co/npm/jy-transform/ +[npm-downloads-image]: https://nodei.co/npm-dl/jy-transform.png?height=2&months=9 + +[bithound-url]: https://www.bithound.io/github/deadratfink/jy-transform +[bithound-code-image]: https://img.shields.io/bithound/code/github/deadratfink/jy-transform.svg?style=flat +[bitHound-dependencies]: https://www.bithound.io/github/deadratfink/jy-transform/master/dependencies/npm +[bitHound-dependencies-image]: https://img.shields.io/bithound/dependencies/github/deadratfink/jy-transform.svg?style=flat +[bitHound-dev-dependencies-image]: https://img.shields.io/bithound/devDependencies/github/deadratfink/jy-transform.svg?style=flat +[bitHound-score-image]: https://img.shields.io/bithound/score/github/deadratfink/jy-transform.svg?style=flat diff --git a/docs/CONTRIBUTING.md b/readme/CONTRIBUTING.md similarity index 58% rename from docs/CONTRIBUTING.md rename to readme/CONTRIBUTING.md index f99835c..a74979e 100644 --- a/docs/CONTRIBUTING.md +++ b/readme/CONTRIBUTING.md @@ -3,27 +3,23 @@ When contributing as coder, please take care of the following conventions: - Enter yourself in the `contributors` section of _package.json_. -- We strictly follow [Semantic Versioning 2](http://semver.org) rules. -- The `development` branch is the leading branch and is protected. Create bugfix and feature +- Strictly follow [Semantic Versioning 2](http://semver.org) rules. +- The `master` branch is the leading branch, is protected and is the stable branch after a release. + Create `bugfix`, `feature` and `refactor` branches (or fork into you own namespace) and create pull - requests to `development` when finished. Any of these should be prefixed with - `bugfix/#...` or `feature/#...` (followed by issue number and a short, "underscored" + requests to `master` when finished. Any of these should be prefixed with + `bugfix/#...`, `feature/#...` or `refactor/#...` (followed by issue number and a short, "underscored" proper meaning), e.g. - `bugfix/#8_fix_js_reading_with_require` - `feature/#14_multidocument_support` - Remember that name could need to be enclosed in quotes, e.g. ```$ git checkout -b 'feature/#19_...'``` when using git shell command. -- The `master` branch is protected and is the stable branch after a release. - It will never be pushed directly (only on release build). -- Indention for any file is 4 SPACEs. - Keep code coverage high (> 95%). -- Doc everything with [JSDocs](http://usejsdoc.org/) and document concepts in +- Document everything with [JSDoc](http://usejsdoc.org/) and describe concepts in [README.md](https://github.com/deadratfink/jy-transform/blob/development/README.md) or [Wiki](https://github.com/deadratfink/jy-transform/wiki). -- Use _single_ parenthesis (`'...'`) in _*.js_ files instead of _double_ parenthesis (`"..."`). -- Avoid the of use parenthesis for keys in JSON objects. -- Use the strict mode (`'use strict';`) in _*.js_ files. +- Coding style is defined by _.eslintrc.js_ and _.editorconfig_. - File names should be lower-case with hyphens as divider, e.g. _options-handler.js_. - Markdown documentation files should be upper-case with _.md_ as extension, placed in _./docs_, e.g. _USAGE.md_. The _README.md_ is build up by these files concatenated diff --git a/readme/DOCUMENTATION.md b/readme/DOCUMENTATION.md new file mode 100644 index 0000000..c397522 --- /dev/null +++ b/readme/DOCUMENTATION.md @@ -0,0 +1,661 @@ +## Why This Module? + +After struggling with some huge YAML file and accidentally +occurring wrong indentations which results in an annoying investigation hell, +I decided to get rid of the YAML file and therefore, create a module which +should be aimed as the swiss army knife for transforming YAML, JS and JSON +types into each other format. + +## CLI in 3 Seconds + +### File Transformation + +E.g. transform YAML content file to a JSON file with an indention of 4: + +```text +$ jyt foo/bar.yaml -t json -i 4 +``` + +## API in a Minute + +### Transformation from Source to Destination + +```javascript +import { transform } from 'jy-transform'; + +const options = { + src: 'foo/bar.yaml', // E.g. read from YAML file... + transform: async (object) => { // ...with exchanging value... + object.foo = 'new value'; + return object; + }, + dest: 'foo/bar-transformed.json', // ...to a new JSON file. + indent: 4, // Ensure an indentation of 4. +}; + +// ---- Promise style: + +transform(options) // Transform, of course, inside an async. + .then(console.log) // Success message! + .catch(console.error); // Oops! + + +// ---- async/await style: + +try { + const msg = await transform(options); // Transform, of course, inside an async. + console.log(msg); // Success message! +} catch (err) { // Oops! + console.error(err); +} +``` + +### Read into JS object from particular Source (File, Stream or JS Object) + +```javascript +import { read } from 'jy-transform'; + +const options = { src: 'foo/bar.yaml' }; // E.g. read from file. + +// ---- Promise style: + +read(options) + .then((object) => console.log(JSON.stringify(object))) // Print read object. + .catch(console.error); + +// ---- async/await style: + +try { + const object = await read(options); + console.log(JSON.stringify(object)); // Print read object. +} catch (err) { + console.error(err); +} +``` + +### Write JS object to particular Destination + +```javascript +import { write } from 'jy-transform'; + +const options = { dest: 'foo/bar.yaml' }; // E.g. write to file. + +// ---- Promise style: + +write(object, options) + .then(console.log) // Print write success message. + .catch(console.error); + +// ---- async/await style: + +try { + const msg = await write(object, options); + console.log(msg); // Print write success message. +} catch (err) { + console.error(err); +} +``` + +## Usage + +The module can be used on CLI or as API (the latter is fully +[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)/`async` based). + +### Usage Types + +Since the module can be used in two different ways, use installation as follows: + +- CLI: install globally via `-g` option +- API: install locally + +Both usage types are described in more detail in the following sections. + +### Use Cases + +So, what are the typical use cases for this module? In terms of _transformation_ +these consists of different phases: + + 1. Reading from source + 2. Transforming JSON objects or apply dedicated actions on the intermediate JSON objects + 3. Writing to a destination + +#### Read Case + +Reading from a file: + +- _*.yaml_ +- _*.js_ +- _*.json_ + +Additionally, on API level from: + +- `stream.Readable` (stream2): + - Contains serialized JS, JSON or YAML + - If not a file stream then setting requires `options.origin` property is mandatory + - Reads as UTF-8 +- JS `Object`: + - Actually, this means the reading phase is "skipped", because object is in-memory already + - Of course, this case _cannot_ not be applied to serialized JSON or YAML content + +#### Transformation Case + +The _transformation_ is usually a format change, but can also be refer to content changes on the +intermediate JS object, the latter with the help of a configurable `transform` callback function. +All possible directions are: + +- YAML ⇒ JS +- YAML ⇒ JSON +- JS ⇒ YAML +- JSON ⇒ YAML +- JS ⇒ JSON +- JSON ⇒ JS +- YAML ⇒ YAML +- JSON ⇒ JSON +- JS ⇒ JS + +while: + +- [YAML](http://http://yaml.org/) = _*.yaml_, _*.yml_ +- [JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript) = _*.js_ (JS object) +- [JSON](http://json.org) = _*.json_ (JS object serialized as JSON) + +As mentioned above a configurable `transform` callback can apply particular actions on the intermediate JS object, but +this is an optional part for [transformation](#transformation) phase. + +#### Write Case + +Writing to a file: + +- _*.yaml_ +- _*.js_ +- _*.json_ + +Additionally, on API level to: + +- `stream.Writable` (stream2) implementations: + - Serialized JS, JSON and YAML + - Requires `options.target` property set + - Writes UTF-8 +- JS `Object`: + - JS as a simple reference + - YAML and JSON as a serialized string + +### Origin and Target Type Inference + +This module supports automatic type inference from file extensions as shown by the following table (from-to): + +| File Extension | Type | +| --- | --- | +| _*.yaml_ | _yaml_ | +| _*.yml_ | _yaml_ | +| _*.js_ | _js_ | +| _*.json_ | _json_ | + +> **NOTE:** if you have files without an extension or e.g. _*.txt_ you _have_ to specify the `origin` or `target` type! + +### Limitations + +- Since this module is build to transform from and to different type formats, any + `Function`s residing in JS type objects are _not_ supported, e.g. transforming + + ```javascript + export const foobar = { + fooKey: 'foo', + fooFunction: function foo() { + //... + } + } + ``` + + to JSON would simply result in + + ```json + { + "fooKey": "foo" + } + ``` + + while transforming to YAML type would even result in an `Error`, e.g. printed + on CLI usage like this: + + ``` + ERROR: YAMLException: unacceptable kind of an object to dump [object Function] + ``` + +- Multidocument handling would be a cool feature which refers in general to YAML + and JS only, but at the moment we require that each document to transform is a + _single_ one per source (or in case of JS could be identified)! This feature is + planned and reflected in [#14](https://github.com/deadratfink/jy-transform/issues/14). + +### CLI Usage + +The CLI provides the `jyt` command which requires the use of some options. +After the global installation you can access the command options +with the usual `--help` option which prints an overview about all +available CLI properties: + +``` +$ jyt --help +Usage: + jyt.js INPUT-FILE [OUTPUT-FILE] [OPTIONS] + +Options: + -o, --origin [STRING] The origin type of INPUT-FILE: [ js | json | yaml ]. (Default is if not given, the type is tried to be inferred from the extension of source path, else it is 'yaml') + -t, --target [STRING] The target type of OUTPUT-FILE: [ js | json | yaml ]. (Default is if not given, the type is tried to be inferred from the extension of destination path, else it is 'js') + -i, --indent [NUMBER] The indentation for pretty-print: 1 - 8. (Default is 4) + -f, --force Force overwriting of existing output files on write phase. When files are not overwritten (which is default), + then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in + case of foo.yaml it would be foo(1).yaml. + -m, --imports STRING Define a 'module.exports[.identifier] = ' identifier (to read from JS _source_ file only, must be a valid JS + identifier!). + -x, --exports STRING Define a 'module.exports[.identifier] = ' identifier (for usage in JS destination file only, must be a valid JS + identifier!). + -k, --no-color Omit color from output + --debug Show debug information + -v, --version Display the current version + -h, --help Display help and usage details +``` + +#### CLI Args + +The ARGS are more formally defined in the following table: + +| Arg | Type | Description | Default | Required | +| --- | --- | --- | --- | --- | +| `INPUT-FILE` | URI | The source file path for transformation. | - | yes | +| `OUTPUT-FILE` | URI | The destination file path to transform to. | When this options is omitted then the output file is stored relative to the input file (same base name but with another extension if type differs). If input and output type are the same then the file overwriting is handled depending on the `--force` value! | no | + +> **NOTE:** the _input file_ has to be specified and should be _first_ argument (in fact, it can be +> anywhere but it must be _before_ an _output file_ argument)! + +#### CLI Options + +The OPTIONS are more formally defined in the following table: + +| Option (short) | Option (long) | Type | Description | Default | Required | +| --- | --- | --- | --- | --- | --- | +| `-o` | `--origin` | string of: [ _js_ | _json_ | _yaml_ ] | The transformation origin type. | if not given, the type is tried to be inferred from the extension of source path, else it is _yaml_ | no | +| `-t` | `--target` | string of: [ _js_ | _json_ | _yaml_ ] | The transformation target type. | if not given, the type is tried to be inferred from the extension of destination path, else it is _js_ | no | +| `-i` | `--indent` | integer
[ 1 - 8 ]
| The code indentation used in destination files. | 2 | no | +| `-f` | `--force` | n/a | Force overwriting of existing output files on write phase. When files are not overwritten (which is default), then the next transformation with same output file name gets a consecutive number on the base file name, e.g. in case of _foo.yaml_ it would be _foo(1).yaml_. | _false_ | no | +| `-m` | `--imports` | string | Define a 'module.exports[.identifier] = ' identifier (to read from JS _source_ file only, must be a valid JS identifier!) | _undefined_ | no | +| `-x` | `--exports` | string | Define a 'module.exports[.identifier] = ' identifier (for usage in JS _destination_ file only, must be a valid JS identifier!) | _undefined_ | no | +| `-k` | `--no-color` | n/a | Omit color from output. | _color_ | no | +| n/a | `--debug` | n/a | Show debug information. | _false_ | no | +| `-v` | `--version` | n/a | Display the current version. | n/a | no | +| `-h` | `--help` | n/a | Display help and usage details. | n/a | no | + +### Examples + +Now we know which properties can be applied on CLI it's time for some examples. Let's assume we +have a YAML content located in _foo.yaml_ holding this data: + +```yaml +foo: bar +``` +#### Example: YAML ⇒ JSON + +Then we can transform it to a JSON content as _foo.json_ file: + +```json +{ + "foo": "bar" +} +``` + +simply by using this command: + +```text +$ jyt foo.yaml -t json -i 4 +``` + +In this example we have overwritten the standard _target_ type (which is `js`) +and applying an _indent_ of 4 SPACEs instead of the default (which is 2). As default the output +file _foo.json_ is written relative to the input file (by omitting the +`dest` option here). + +> **NOTE:** here you _have_ to provide the target with option `-t json` or else the +> default `js` would have been applied! + +If the source would have been a `js` type like in this example + +``` +$ jyt foo.js -t json -i 4 +``` + +then the `js` value for `origin` is automatically inferred from file extension. +Accordingly, this is also true for the `target` option. + +#### Example: JSON ⇒ JS + +The command + +```text +$ jyt foo.json +``` +results in _foo.js_: +```javascript +export default {foo: 'bar'} +``` + +#### Example: JS ⇒ YAML + +The command + +```text +$ jyt foo.js -t yaml +``` + +results in _foo.yaml_: + +```yaml +foo: bar +``` + +#### Example: Transformation with Different Destination + +Simply specify the _output_ file with a different file name: + +```text +$ jyt foo.json results/foobar.yaml +``` + +#### Example: Transformation with Unsupported Source File Extension + +As said, normally we infer from file extension to the type, but assume the source +file has a file name which does not imply the type (here a JSON +type in a TEXT file), then you can simply provide the `-o` option with the +correct `origin` type: + +```text +$ jyt foo.txt foobar.yaml -o json +``` + +> **NOTE:** of course, the `-t` (`--target`) option works analogous. + +#### Example: Read from File with Exports Identifier + +It could be that a JS source `exports` several objects and you want to read +from exactly the one you specify, then provide the `-m` (`--imports`) option. + +In this this example we have a _foo.js_ file exporting _two_ objects: + +```javascript +export const foo = { + foo: 'bar' +}; + +export const bar = { + bar: 'foo' +}; +``` +but you want to convert only `bar` object, then call: + +```text +$ jyt foo.js bar.yaml -m bar +``` + +to get the YAML result: + +```yaml +bar: foo +``` + +> **NOTE:** the same applies on API level when using JS objects as `dest`: +> +> ```javascript +> const fooBar = { +> foo: 'bar', +> bar: 'foo' +> }; +> +> const options = { +> src: fooBar, +> dest: {}, +> exports: 'bar' +> }; +> +> //...transform +> ``` +> +> The transformation will result in this in-memory object: +> +> ```javascript +> bar: { +> foo: 'bar', +> bar: 'foo' +> } +> ``` +> +> Of course, as sub-node of `options.dest`. + +#### Example: Write Exports Identifier for JS File + +Assume you want to generate a JS file with an exports string which gets an +identifier. We reuse the YAML file from above: + +```yaml +foo: bar +``` + +using this command: + +``` +$ jyt foo.yaml foobar.js -x foobar +``` + +This generates the following output in JS file using `foobar` as identifier: + +```javascript +export const foobar = { + foo: 'bar' +} +``` + +> **NOTE:** the identifier must be a valid JS identifier accoding to ECMAScript 6 +> (see also [Valid JavaScript variable names in ECMAScript 6](https://mathiasbynens.be/notes/javascript-identifiers-es6) +> and [Generating a regular expression to match valid JavaScript identifiers](https://mathiasbynens.be/demo/javascript-identifier-regex)). + +#### Example: Force Overwriting + +> **IMPORTANT NOTE:** when using this feature then any subsequent execution which +> uses the same target/file name, can _overwrite_ the original source or target created beforehand! + +Therefore, this feature is _not_ enabled by default to prevent you from accidentally +overwriting your input source or already generated targets. + +But let's say we want to overwrite the original source now because you want +to change the indentation from 2 to 4 SPACEs, then we can do this as follows: + +```text +$ jyt foo.js -i 4 -f +``` + +> **NOTE:** the other way round (i.e. leaving out the `-f` (`--force`)) switch would create a _new file_ relatively to +> the `src` _foo.js_, named as _foo(1).js_; note the consecutive number! Naturally, +> another run of the command would result in a file called _foo(2).js_ and so forth. + +### API Usage + +Since the usage on CLI is a 2-step process: + + 1. Read from source file to JS object ⇒ + 2. Write out (maybe to other type) + +the direct API calls additionally provide the optional usage of a `transform` function +where you can alter the intermediate JS object before it is written and therefore, turns +this into a 3-step process: + + 1. Read from source ⇒ + 2. Transform the JS object ⇒ + 3. Write out (maybe to other type) + +For more details about this and all the functions provided by this module please refer to the +[API Reference](https://github.com/deadratfink/jy-transform/wiki/API-v3). + +The `origin` and `target` type inference is also standard for the API level. + +> **HINT:** of course, if you like you can use the `read`and `write` functionality solely besides any transformation needs. + +#### API Properties + +The public `transform` function (that does not mean the optional `transform` callback here!) takes +the necessary `options` for the transformation: + +```javascript +[async] function transform(options) +``` + +#### Options + +For a detailed description see: + +- [Read Options](API-PUBLIC.md#readoptions--codeobjectcode) +- [Write Options](API-PUBLIC.md#writeoptions--codeobjectcode) +- [Transform Options](API-PUBLIC.md#transformoptions--codeobjectcode) + +##### Example + +```javascript +const options = { + src: 'foo.json', + origin: 'json', // actually, not needed here, inferred from src's extension automatically! + dest: './foo/bar.yaml', + target: 'yaml', // actually, not needed here, inferred from dest's extension automatically! + indent: 4 +} +``` + +#### Using Transform Callback + +The `transform` property is optional but if provided it must be of type `Function` and could but must not be +a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)/`async`. + +- When being a Promise it has to resolve with the `data` object or reject with an `Error` based object. +- In case of normal or `async` function return `data` or throw an `Error`. + +One of the easiest ones is the identity function + +_f(data) → data_ + +which could be expressed as follows: + +```javascript +const identity = data => data; +``` + +Of course, this would have no effect on the provided JS data. Actually, this one is +used internally as default when no `transform` function is configured to ensure the proper +control flow. + +OK, lets go back to a more practical example, e.g. we want to alter the value of a +JS property before it is written to a file. Assuming we have this piece of YAML +as input from a file called _src.yaml_: + +```yaml +foo: old bar +``` + +Applying this `transform` callback option to the `src` content + +```javascript +import { transform } from 'jy-transform'; + +const options = { + src: 'src.yaml', + dest: 'result.json', + transform: async (data) => { + data.foo = 'new bar'; + return data; + } +}; + +transform(options) + .then(console.log) + .catch(console.error); +``` + +will result in such JSON file content: + +```json +{ + "foo": "new bar" +} +``` + +Of course, in real world scenarios you will have use cases which usually have a +higher complexity where one function might be insufficient or at least +inconvenient. but this does not raise a problem at all, because you can create +several functions to be applied in the whole transformation process by gathering +them in one function. + +Let's assume we have some Promise functions to apply. For simplicity reasons we +simulate these for the moment by some functions, each adding a key-value to the +given (initially empty) JS object. + +> **NOTE:** each of them has to resolve with the `data` object! + +```javascript +const key1 = async (data) => { + data.key1 = 'value1'; + return data; +}; + +const key2 = async (data) => { + data.key2 = 'value2'; + return data; +}; + +const key3 = async (data) => { + data.key3 = 'value3'; + return data; +}; +``` + +These can be collected by different aggregation or composition functions of the underlying +Promise framework, e.g. using the [`Promise.all([...])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) +function. This one can collect all three functions above and ensure their proper execution: + +```javascript +import { transform } from 'jy-transform'; + +const options = { + src: {}, + transform: (data) => Promise.all([key1(data), key2(data), key3(data)]) + .then(result => result[result.length - 1]) +}; + +transform(options) + .then(console.log) + .catch(console.error); +``` + +The result in the `transform` function can be retrieved from the returned +array, i.e. in case of [`Promise.all([...])`](http://bluebirdjs.com/docs/api/promise.all.html) +you have to pick the _last_ element which contains the "final product". + +From our example above it would be result in this object + +```javascript +{ + key1: 'value1', + key2: 'value2', + key3: 'value3' +} +``` + +which is passed back to the transformation chain. Following this pattern +you can do almost everything with the JS object, like + +- Deleting properties +- Changing properties to other types +- Validating and throwing/resolving with error if not valid +- ... + +## Contributing + +Pull requests and stars are always welcome. For bugs and feature requests, please create an +[issue](https://github.com/deadratfink/jy-transform/issues) or create a PR. +See the wiki [Contributing](https://github.com/deadratfink/jy-transform/wiki/Contributing) +section for more details about conventions. + diff --git a/docs/LOGO.md b/readme/LOGO.md similarity index 100% rename from docs/LOGO.md rename to readme/LOGO.md diff --git a/readme/QA.md b/readme/QA.md new file mode 100644 index 0000000..9c13979 --- /dev/null +++ b/readme/QA.md @@ -0,0 +1,22 @@ +## Master Branch Coverage + +### Grid + +Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively. + +![codecov.io](https://codecov.io/gh/deadratfink/jy-transform/branch/master/graphs/tree.svg) + +### Sunburst +The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is represented by the number of statements and the coverage, respectively. + +![codecov.io](https://codecov.io/gh/deadratfink/jy-transform/branch/master/graphs/sunburst.svg) + +### Icicle + +The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is represented by the number of statements and the coverage, respectively. + +![codecov.io](https://codecov.io/gh/deadratfink/jy-transform/branch/master/graphs/icicle.svg) + + + + diff --git a/src/cli.js b/src/cli.js new file mode 100755 index 0000000..5aee982 --- /dev/null +++ b/src/cli.js @@ -0,0 +1,150 @@ +import path from 'path'; +import cli from 'cli'; +import Package from '../package.json'; +import { + DEFAULT_INDENT, + DEFAULT_FORCE_FILE_OVERWRITE, + DEFAULT_JS_IMPORTS_IDENTIFIER, + DEFAULT_JS_EXPORTS_IDENTIFIER, + ORIGIN_DESCRIPTION, + TARGET_DESCRIPTION, + TYPE_JS, + TYPE_JSON, + TYPE_YAML, + DEFAULT_STRICT, + DEFAULT_ES5, + DEFAULT_DOUBLE_QUOTES, +} from './constants'; +import { transform } from './transformer'; + +/** + * @module jy-transform:jyt + * @description The command line interface. + * @private + */ + +// //////////////////////////////////////////////////////////////////////////// +// CLI INIT +// //////////////////////////////////////////////////////////////////////////// + +/** + * How to use the CLI. + * + * @type {string} + * @private + */ +const usage = Object.keys(Package.bin)[0] + ' INPUT-FILE [OUTPUT-FILE] [OPTIONS]'; + +/** + * The path to package.json. + * + * @type {string} + * @private + */ +const packagePath = path.join(__dirname, '../package.json'); + +/** + * The options description for parsing the command line input, must be an object with opts defined like: + * ``` + * long_tag: [short_tag, description, value_type, default_value]; + * ``` + * @type {{origin: Array, target: Array, src: Array, dest: Array, indent: Array, force: Array, imports: Array, exports: + * Array}} + * @private + */ +const cliOptionsSchema = { + origin: [ + 'o', 'The origin type of INPUT-FILE: [ ' + TYPE_JS + ' | ' + TYPE_JSON + ' | ' + TYPE_YAML + ' ]', + 'string', ORIGIN_DESCRIPTION], + target: [ + 't', 'The target type of OUTPUT-FILE: [ ' + TYPE_JS + ' | ' + TYPE_JSON + ' | ' + TYPE_YAML + ' ]', + 'string', TARGET_DESCRIPTION], + indent: ['i', 'The indention for pretty-print: 1 - 8', 'int', DEFAULT_INDENT], + force: [ + 'f', 'Force overwriting of existing output files on write phase: when files are not overwritten (which' + + ' is default), then the next transformation with same output file name gets a consecutive number on the base' + + ' file name, e.g. in case of foo.yaml it would be foo(1).yaml', 'boolean', DEFAULT_FORCE_FILE_OVERWRITE], + imports: [ + 'm', 'Define an identifier for object (to read as "export const identifier / module.exports[.identifier]"' + + ' from JS source file only, must be a valid JS identifier!)', 'string', DEFAULT_JS_IMPORTS_IDENTIFIER], + exports: [ + 'x', 'Define an identifier for object (write to "export const identifier / module.exports[.identifier]"' + + ' in JS destination file only, must be a valid JS identifier!)', 'string', DEFAULT_JS_EXPORTS_IDENTIFIER], + strict: [ + 's', 'Whether to write a "use strict;" in JS type output', + 'boolean', DEFAULT_STRICT], + es5: [ + false, 'Whether not to use ECMAScript6 syntax for JS type output like "module.exports" instead of ' + + '"export default", applicable only for JS output', 'boolean', DEFAULT_ES5], + double: [ + false, 'Whether not to use single-quotes style for values in JS type output (i.e. double-quotes)', + 'boolean', DEFAULT_DOUBLE_QUOTES], +}; + +/** + * Prints the error to console and exits process with 1. + * + * @param {string|Error} err - The error to print. + * @private + */ +function error(err) { + cli.error('////////////////////////////////////////////////////////////////////////////////'); + cli.error(err); + if (err.stack) { + cli.debug(err.stack); + } + cli.error('////////////////////////////////////////////////////////////////////////////////\n'); + cli.getUsage(1); +} + +/** + * The main entry callback. When calling `cli.main()` this receives the `options` + * given on CLI, then does the transformation with these options and finally, it + * prints the result to the CLI. + * + * @param {Array} args - The first mandatory argument is the input file (`args[0]`), the second (optional) + * argument is the output file (`args[1]`). + * @param {module:jy-transform:type-definitions~TransformOptions} cliOptions - The options provided via CLI. + * @private + */ +function main(args, cliOptions) { + // read file args and set to options + + if (args.length > 0) { + cli.debug('input file: ' + args[0]); + cliOptions.src = args[0]; // eslint-disable-line prefer-destructuring + } else { + error('please specify an input file as first argument!'); + } + if (args.length > 1) { + cli.debug('output file: ' + args[1]); + cliOptions.dest = args[1]; // eslint-disable-line prefer-destructuring + } else { + cli.debug('output file not specified, using default'); + } + + // We have to set to undefined if the values are not given on CLI or else Joi will fail + // because it will receive the description created in options object above! + if (cliOptions.origin === ORIGIN_DESCRIPTION) { + cliOptions.origin = undefined; + } + if (cliOptions.target === TARGET_DESCRIPTION) { + cliOptions.target = undefined; + } + + cli.debug('Passed options:\n' + JSON.stringify(cliOptions, null, 4)); + + transform(cliOptions) + .then(cli.info) + .catch(error); +} + +/* + * Init the CLI instance. + */ +cli.width = 120; +cli.setUsage(usage); +cli.setApp(packagePath); +cli.enable('version', 'status', 'timeout'); +cli.parse(cliOptionsSchema); +cli.main(main); diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..35e198f --- /dev/null +++ b/src/constants.js @@ -0,0 +1,175 @@ +/** + * @module jy-transform:constants + * @description Useful constants used for the module and its usage. + * @public + */ + +/** + * The 'utf8' constant. + * @type {string} + * @constant + * @private + */ +export const UTF8 = 'utf8'; + +/** + * The `'yaml'` type constant. + * @type {string} + * @constant + * @public + */ +export const TYPE_YAML = 'yaml'; + +/** + * The `'json'` type constant. + * @type {string} + * @constant + * @public + */ +export const TYPE_JSON = 'json'; + +/** + * The `'js'` type constant. + * @type {string} + * @constant + * @public + */ +export const TYPE_JS = 'js'; + +/** + * A map for extensions to type. + * + * @type {{yml: string, yaml: string, js: string, json: string}} + * @private + */ +export const EXT_TO_TYPE_MAP = { + yml: TYPE_YAML, + yaml: TYPE_YAML, + js: TYPE_JS, + json: TYPE_JSON +}; + +/** + * The default file indention (4 SPACEs). + * @type {number} + * @constant + * @private + */ +export const DEFAULT_INDENT = 2; + +/** + * The minimum file indention (0 SPACE) fo JS and JSON types. + * @type {number} + * @constant + * @private + */ +export const MIN_INDENT = 0; + +/** + * The minimum file indention (0 SPACE) for YAML types. + * @type {number} + * @constant + * @private + */ +export const MIN_YAML_INDENT = 2; + +/** + * The maximum file indention (8 SPACEs). + * @type {number} + * @constant + * @private + */ +export const MAX_INDENT = 8; + +/** + * The default `origin` value: 'yaml'. + * @type {string} + * @constant + * @private + */ +export const DEFAULT_ORIGIN = TYPE_YAML; + +/** + * The default `origin` value: 'js'. + * @type {string} + * @constant + * @private + */ +export const DEFAULT_TARGET = TYPE_JS; + +/** + * Whether to overwrite existing file or object on output. + * @type {boolean} + * @constant + * @private + */ +export const DEFAULT_FORCE_FILE_OVERWRITE = false; + +/** + * Whether to write a "use strict;" in JS type output. + * @type {boolean} + * @constant + * @private + */ +export const DEFAULT_STRICT = false; + + +/** + * Whether _not_ to use ECMAScript6 syntax for JS type output. + * @type {boolean} + * @constant + * @private + */ +export const DEFAULT_ES5 = false; + +/** + * Whether _not_ to use single-quotes style for values in JS type output (i.e. double-quotes). + * @type {boolean} + * @constant + * @private + */ +export const DEFAULT_DOUBLE_QUOTES = false; + +/** + * The `origin` description value. + * @type {string} + * @constant + * @private + */ +export const ORIGIN_DESCRIPTION = 'if not given, the type is tried to be inferred from the extension of source path, ' + + 'else it is \'' + DEFAULT_ORIGIN + '\''; + +/** + * The `target` description value. + * @type {string} + * @constant + * @private + */ +export const TARGET_DESCRIPTION = 'if not given, the type is tried to be inferred from the extension of destination' + + ' path, else it is \'' + DEFAULT_TARGET + '\''; + +/** + * The `dest` description value. + * @type {string} + * @constant + * @private + */ +export const DEST_DESCRIPTION = 'storing relative to input file'; + +/** + * The `src` exports identifier value to read. + * @type {string} + * @private + * @constant + * @example + * module.exports.foo = {...}; // here 'foo' is the identifier for an object to read from the source! + */ +export const DEFAULT_JS_IMPORTS_IDENTIFIER = undefined; + +/** + * The `dest` exports identifier value to write. + * @type {string} + * @private + * @constant + */ +export const DEFAULT_JS_EXPORTS_IDENTIFIER = undefined; diff --git a/src/io-utils.js b/src/io-utils.js new file mode 100644 index 0000000..da9be57 --- /dev/null +++ b/src/io-utils.js @@ -0,0 +1,234 @@ +import mkdirp from 'mkdirp-then'; +import path from 'path'; +import fs from 'fs'; +import { Buffer } from 'buffer'; +import jsYaml from 'js-yaml'; +import promisify from 'promisify-es6'; +import { + UTF8, + TYPE_JS, + TYPE_JSON, +} from './constants'; + +/** + * Promisified `fs` module. + * @private + */ +const fsPromisified = promisify(fs); + +/** + * @module jy-transform:io-utils + * @description This module provides a helper I/O interface for files, streams or `Object`. + * @private + */ + +/** + * Reads JS or JSON from file, optionally choosing a property given by `imports`. + * + * @param {string} file - The file path. + * @param {string} [imports] - An object which is exported in the file. + * @returns {Object} The read object from `file`. + * @throws {Error} When an `imports` is given but the declared object key is not exported by the file. + * @private + */ +export function readJsOrJsonFromFile(file, imports) { + const resolvedPath = path.resolve('', file); + if (imports) { + // eslint-disable-next-line import/no-dynamic-require, global-require + const object = require(resolvedPath)[imports]; + if (!object) { + throw new Error(`An identifier string '${imports}' was specified for JS object ` + + 'but could not find this object, pls ensure that file ' + file + ' exports it.'); + } + return object; + } + // eslint-disable-next-line import/no-dynamic-require, global-require + return require(resolvedPath); // reads both: JS and JSON! +} + +/** + * Reads JS from JS object, optionally choosing a property given by `imports`. + * + * @param {string} object - The JS object source. + * @param {string} [imports] - An object which is is a property/sub-object in `object`. + * @returns {Object} The given but cloned `object` or any property/sub-object specified by `imports`. + * @throws {Error} When an `imports` is given but the declared object key is not contained in the source object. + * @private + */ +export function readJsFromObject(object, imports) { + if (imports) { + const subObject = object[imports]; + if (!subObject) { + throw new Error(`An identifier string '${imports}' was specified for property/JS sub-object ` + + 'but could not be found, pls ensure that object source contains it.'); + } + return Object.assign({}, subObject); // clone, do not alter original object! + } + return Object.assign({}, object); // clone, do not alter original object! +} + +/** + * Reads YAML from file. + * + * @param {string} file - The YAML file source. + * @returns {Object} The read JS object from YAML file. + * @throws {Error} When any I/O error occurs while the source file. + * @private + */ +export async function readYamlFromfile(file) { + const yaml = await fsPromisified.readFile(file, UTF8); + return jsYaml.safeLoad(yaml); +} + +/** + * Reads from a passed stream and resolves by callback. + * + * @param {Stream.Readable} readable - The source to read from. + * @param {string} origin - Origin type, must be 'yaml' or 'json'/'js'. + * @returns {Promise.} The read content as JS object representation. + * @see {@link TYPE_YAML} + * @see {@link TYPE_JSON} + * @see {@link TYPE_JS} + * @private + */ +export function readFromStream(readable, origin) { + return new Promise((resolve, reject) => { + const buffers = []; + readable + .on('data', data => buffers.push(data)) + .on('error', reject) + .on('end', () => { + const buffer = Buffer.concat(buffers); + try { + if (origin === TYPE_JS || origin === TYPE_JSON) { + resolve(JSON.parse(buffer.toString(UTF8))); + } else { // Validation allows only YAML here! + resolve(jsYaml.safeLoad(buffer.toString(UTF8))); + } + } catch (err) { // probably a SyntaxError for JSON or a YAMLException + readable.emit('error', err); // send to .on('error',... + } + }); + }); +} + +/** + * Turns the destination file name into a name containing a consecutive + * number if it exists. It iterates over the files until it finds a file + * name which does not exist. + * + * @param {string} dest - The destination file. + * @returns {string} A consecutive file name or the original one if `dest` file does not exist. + * @private + */ +function getConsecutiveDestName(dest) { + let tmpDest = dest; + let i = 0; + const destDirName = path.dirname(tmpDest); + const ext = path.extname(tmpDest); + const basename = path.basename(tmpDest, ext); + while (fs.existsSync(tmpDest)) { + tmpDest = path.join(destDirName, basename + '(' + (i += 1) + ')' + ext); + } + return tmpDest; +} + +/** + * Ensures that all dirs exists for file type `dest` and writes the JS object to file. + * + * @param {string} data - The object to write into file. + * @param {string} dest - The file destination path. + * @param {string} target - The target type, one of [ 'yaml' | 'json' | 'js' ]. + * @param {boolean} [forceOverwrite=false] - Forces overwriting the destination file if `true`. + * @see {@link TYPE_YAML} + * @see {@link TYPE_JSON} + * @see {@link TYPE_JS} + * @private + */ +async function mkdirAndWrite(data, dest, target, forceOverwrite = false) { + const destDir = path.dirname(dest); + await mkdirp(destDir); + let finalDestination = dest; + if (!forceOverwrite) { + finalDestination = getConsecutiveDestName(dest); + } + await fsPromisified.writeFile(finalDestination, data, UTF8); + return 'Writing \'' + target + '\' file \'' + finalDestination + '\' successful.'; +} + +/** + * Writes a serialized object to file. + * + * @param {string} data - The object data to write into file. + * @param {string} dest - The file destination path. + * @param {string} target - The target type, one of [ 'yaml' | 'json' | 'js' ]. + * @param {boolean} [forceOverwrite] - Forces overwriting the destination file if `true`. + * @see {@link TYPE_YAML} + * @see {@link TYPE_JSON} + * @see {@link TYPE_JS} + * @returns {Promise.} Containing the write success message to handle by caller (e.g. for logging). + * @throws {Error} If serialized JSON file could not be written due to any reason. + * @private + */ +export async function writeToFile(data, dest, target, forceOverwrite) { + let stats; + try { + stats = await fsPromisified.stat(dest); + } catch (_) { + // ignore error (because file could possibly not exist at this point of time) + } + if (stats && stats.isDirectory()) { + throw new Error(`Destination file '${dest}' is a directory, pls specify a valid file resource!`); + } + return mkdirAndWrite(data, dest, target, forceOverwrite); +} + +/** + * Writes a string serialized data object to a `stream.Transform`. + * + * @param {string} data - The data to write into stream. + * @param {stream.Transform} writable - The stream destination. + * @param {string} target - The target type, one of [ 'yaml' | 'json' | 'js' ]. + * @see {@link TYPE_YAML} + * @see {@link TYPE_JSON} + * @see {@link TYPE_JS} + * @returns {Promise.} Containing the write success message to handle by caller (e.g. for logging). + * @throws {Error} If serialized JS object could not be written due to any reason. + * @private + */ +export function writeToStreamTransform(data, writable, target) { + return new Promise((resolve, reject) => { + writable + .on('error', reject) + .on('finish', () => resolve('Writing ' + target + ' to stream successful.')); + + // write stringified data + writable.write(data); + writable.end(); + }); +} + +/** + * Writes a string serialized data object to a `stream.Writable`. + * + * @param {string} data - The data to write into stream. + * @param {stream.Writable|stream.Duplex} writable - The stream destination. + * @param {string} target - The target type, one of [ 'yaml' | 'json' | 'js' ]. + * @see {@link TYPE_YAML} + * @see {@link TYPE_JSON} + * @see {@link TYPE_JS} + * @returns {Promise.} Containing the write success message to handle by caller (e.g. for logging). + * @throws {Error} If serialized JS object could not be written due to any reason. + * @private + */ +export function writeToStreamWritable(data, writable, target) { + return new Promise((resolve, reject) => { + writable + .on('error', reject) + .on('finish', () => resolve('Writing ' + target + ' to stream successful.')); + + // write stringified data + writable.write(data); + writable.end(); + }); +} diff --git a/src/jy-transform.js b/src/jy-transform.js new file mode 100755 index 0000000..8209a78 --- /dev/null +++ b/src/jy-transform.js @@ -0,0 +1,170 @@ +import { transform as _transform } from './transformer'; +import { read as _read } from './reader'; +import { write as _write } from './writer'; +import { + TYPE_YAML as _TYPE_YAML, + TYPE_JS as _TYPE_JS, + TYPE_JSON as _TYPE_JSON, +} from './constants'; + +/** + * @module jy-transform + * @description This module provides the _public_ interface for the _read_, _write_ and _transform_ functionality. + * @public + * @author Jens Krefeldt + */ + +/** + * The entry method for all transformations accepting a configuration object and + * an (optional) callback function. It executes the transformation logic. + * + * 1. Input (read) + * 2. Transform [ + callback] + * 3. Output (write). + * + * @param {TransformOptions} options - The configuration for a transformation. + * @returns {Promise} The transformation result. + * @resolve {string} With the transformation result as message (e.g. to be logged by caller). + * @reject {ValidationError} If any `options` validation occurs. + * @reject {Error} Will throw any error if read, transform or write operation failed due to any reason. + * @public + * @example + * import { transform } from 'jy-transform'; + * const options = { + * src: 'foo/bar.yaml', // From YAML file... + * transform: async (object) => { // ...callback with exchanging value... + * object.foo = 'new value'; + * return object; + * }, + * target: 'foo/bar-transformed.json', // ...to a new JSON file. + * indent: 4, + * }; + * + * // ---- Promise style: + * + * transform(options) + * .then(console.log) + * .catch(console.error); + * + * + * // ---- async/await style: + * + * try { + * const msg = await transform(options); + * console.log(msg); + * } catch (err) { + * console.error(err.stack); + * }; + */ +export const transform = _transform; +/** + * Reads a particular content type from a source provided in the passed `options`. + * + * @param {ReadOptions} options - The read options. + * @returns {Promise} The result. + * @resolve {string} Resolves with JS object result. + * @reject {ValidationError} If any `options` validation occurs. + * @reject {Error} If any write error occurs. + * @public + * @example + * import { read } from 'jy-transform'; + * + * + * // --- from file path + * + * options = { + * src: 'foo.yml' + * }; + * + * read(options) + * .then(obj => console.log(JSON.stringify(obj))) + * .catch(console.error); + * + * + * // --- from Readable + * + * options = { + * src: fs.createReadStream('foo.yml') + * }; + * + * read(options) + * .then(obj => console.log(JSON.stringify(obj))) + * .catch(console.error); + */ +export const read = _read; + +/** + * Writes the passed JS object to a particular destination described by the passed `options`. + * + * @param {Object} object - The JS source object to write. + * @param {WriteOptions} options - The write options. + * @returns {Promise} The result. + * @resolve {string} With the write success message. + * @reject {Error} If any write error occurs. + * @reject {ValidationError} If any `options` validation occurs. + * @public + * @example + * import { write } from 'jy-transform'; + * + * + * // ---- write obj to file --- + * + * const obj = {...}; + * const options = { + * dest: 'result.js', + * indent: 4 + * } + * + * write(obj, options) + * .then(console.log) + * .catch(console.error); + * + * + * // ---- write obj to Writable --- + * + * options = { + * dest: fs.createWriteStream('result.json'), + * indent: 4 + * } + * + * write(obj, options) + * .then(console.log) + * .catch(console.error); + * + * + * // ---- write obj to object --- + * + * options = { + * dest: {}, + * indent: 4 + * } + * + * write(obj, options) + * .then(console.log) + * .catch(console.error); + */ +export const write = _write; + +/** + * The `'yaml'` type constant. + * @type {string} + * @constant + * @public + */ +export const TYPE_YAML = _TYPE_YAML; + +/** + * The `'js'` type constant. + * @type {string} + * @constant + * @public + */ +export const TYPE_JS = _TYPE_JS; + +/** + * The `'json'` type constant. + * @type {string} + * @constant + * @public + */ +export const TYPE_JSON = _TYPE_JSON; diff --git a/src/reader.js b/src/reader.js new file mode 100644 index 0000000..f021bca --- /dev/null +++ b/src/reader.js @@ -0,0 +1,98 @@ +import isStream from 'is-stream'; +import { readOptionsSchema } from './validation/options-schema'; +import Joi from './validation/joi-extensions'; +import { + readJsFromObject, + readJsOrJsonFromFile, + readYamlFromfile, + readFromStream, +} from './io-utils'; +import { + TYPE_YAML, + TYPE_JS, + TYPE_JSON, +} from './constants'; + +/** + * @module jy-transform:reader + * @description This module provides the _public_ interface for the _read_ functionality of YAML, JS or JSON sources + * (file, `Object` or {@link stream.Readable}). + * @private + */ + +/** + * Reads the data from a given JS or JSON source. + * + * @param {ReadOptions} options - Contains the JS/JSON source reference to read from. + * @returns {Promise.} Contains the read JS object. + * @private + */ +async function readJsOrJson(options) { + if (typeof options.src === 'string') { // path to JSON or JS file + return readJsOrJsonFromFile(options.src, options.imports); + } else if (isStream.readable(options.src)) { + return readFromStream(options.src, TYPE_JSON); // reads both: JS or JSON! + } + // options.src is JS object here! + return readJsFromObject(options.src, options.imports); +} + +/** + * Loads a single YAML source containing document and returns a JS object. + * *NOTE:* this function does not understand multi-document sources, it throws + * exception on those. + * + * @param {ReadOptions} options - Contains the YAML source reference to read from. + * @returns {Promise.} Contains the read JS object. + * @private + */ +async function readYaml(options) { + if (typeof options.src === 'string') { + return readYamlFromfile(options.src); + } + // as validation has passed already, this can only be stream here + return readFromStream(options.src, TYPE_YAML); +} + +// /** +// * Parses string as single YAML source containing multiple YAML document and turns a JS objects array. +// * +// * NOTE: This function does not understand multi-document sources, it throws exception on those. +// * +// * @param src {string} The YAML source to read. +// * @returns {Promise} Containing an array holding the multiple JSON objects. +// * @public +// */ +// Reader.prototype.readYamls = function (src) { +// // load source from YAML source +// return fsPromisified.readFile(src, 'utf8') +// .then(function(yaml) { +// logger.debug('YAML documents loaded from ' + src); // TOD: can this be shortened? -> return +// Promise.resolve(jsYaml.safeLoadAll(yaml)); return Promise.resolve().then(function () { var jsDocs = []; return +// jsYaml.safeLoadAll(yaml, function (doc) { // TOD this will not work in Promise environment!!! +// self.logger.trace(doc); jsDocs.push(doc); }); }); }); }; } + +/** + * Reads a particular content type from a source provided in the passed `options`. + * + * @param {ReadOptions} options - The read options. + * @returns {Promise} The result. + * @resolve {string} Resolves with JS object result. + * @reject {ValidationError} If any `options` validation occurs. + * @reject {Error} If any write error occurs. + * @private + */ +export async function read(options) { + const validatedOptions = await Joi.validate(options, readOptionsSchema); + switch (validatedOptions.origin) { + case TYPE_JS: + case TYPE_JSON: + return readJsOrJson(validatedOptions); + default: + return readYaml(validatedOptions); + } +} + +export default { + read, +}; diff --git a/src/serialize-utils.js b/src/serialize-utils.js new file mode 100644 index 0000000..2838af1 --- /dev/null +++ b/src/serialize-utils.js @@ -0,0 +1,61 @@ +import os from 'os'; +import jsonStringifySafe from 'json-stringify-safe'; +import serializeJs from 'serialize-js'; + +/** + * @module jy-transform:serialize-utils + * @description This module provides an interface for serializing JS either to string or the JSON format. + * @private + */ + +/** + * Creates a potential named `'module.exports[.exportsTo]'` string. + * + * @param {boolean} es5 - Whether to use ECMAScript5 export syntax. + * @param {string} [exportsTo] - The export name. + * @returns {Promise.} Resolves with the exports string. + * @private + */ +export async function createExportString(es5, exportsTo) { + let exports = es5 ? 'module.exports' : 'export'; + if (exportsTo) { + exports += es5 ? '.' + exportsTo + ' = ' : ` const ${exportsTo} = `; + } else { + exports += es5 ? ' = ' : ' default '; + } + return exports; +} + +/** + * Serialize a JS object to string. + * + * @param {Object} object - The JS Object to serialize. + * @param {WriteOptions} options - The write options. + * @returns {Promise.} - Promise resolve with the serialized JS content. + * @private + */ +export async function serializeJsToString(object, options) { + let useStrict = ''; + if (options.strict) { + const quote = options.double ? '"' : '\''; + useStrict = `${quote}use strict;${quote}${os.EOL}${os.EOL}`; + } + const exportsStr = await createExportString(options.es5, options.exports); + return `${useStrict}${exportsStr}${serializeJs.serialize(object, { + indent: options.indent, + forceSingleQuotes: !options.double, + })};${os.EOL}`; +} + +/** + * Serialize a JS object to JSON string. + * + * @param {Object} object - The object to serialize. + * @param {number} indent - The code indention. + * @returns {string} The serialized JSON. + * @private + */ +export async function serializeJsToJsonString(object, indent) { + return jsonStringifySafe(object, null, indent) + os.EOL; +} + diff --git a/src/transformer.js b/src/transformer.js new file mode 100644 index 0000000..465934c --- /dev/null +++ b/src/transformer.js @@ -0,0 +1,40 @@ +import Joi from './validation/joi-extensions'; +import { read } from './reader'; +import { write } from './writer'; +import { transformOptionsSchema } from './validation/options-schema'; + +/** + * @module jy-transform:transformer + * @description This module provides the _transform_ functionality for YAML, JS or JSON source to destination mapping. + * @private + */ + +/** + * The entry method for all transformations accepting a configuration object and + * a (configurable) transformation callback function. + * + * 1. Input (read) + * 2. Transform [ + callback] + * 3. Output (write). + * + * @param {TransformOptions} options - The configuration for a transformation. + * @returns {Promise} The transformation result. + * @resolve {string} With the transformation result as message (e.g. to be logged by caller). + * @reject {ValidationError} If any `options` validation occurs. + * @reject {Error} Will throw any error if read, transform or write operation has failed due to any reason. + * @private + */ +export async function transform(options) { + const validatedOptions = await Joi.validate(options, transformOptionsSchema); + let object = await read(validatedOptions); + object = await validatedOptions.transform(object); + + if (options.dest) { + validatedOptions.dest = options.dest; // Do not loose ref to original object! + } + return write(object, validatedOptions); +} + +export default { + transform, +}; diff --git a/src/type-definitions.js b/src/type-definitions.js new file mode 100644 index 0000000..85c091e --- /dev/null +++ b/src/type-definitions.js @@ -0,0 +1,105 @@ +// ///////////////////////////////////////////////////////////////////////////// +// EXTERNAL DEFINITIONS +// ///////////////////////////////////////////////////////////////////////////// + +/** + * Hapi.js Joi. + * @external joi + * @see {@link https://github.com/hapijs/joi Hapi Joi} + * @private + */ + +/** + * Joi validation error. + * @typedef ValidationError + * @memberof external:joi + * @see {@link hhttps://github.com/hapijs/joi/blob/v10.2.0/API.md#errors Joi errors} + * @public + */ + +/** + * The validation schema. Can be a {@link external:joi} type object or a plain object + * where every key is assigned a {@link external:joi} type object. + * @typedef Schema + * @memberof external:joi + * @see {@link https://github.com/hapijs/joi/blob/v10.2.2/API.md#joi Joi API} + * @private + */ + +/** + * Hapi.js Joi schema extension. + * @typedef Extension + * @memberof external:joi + * @see {@link https://github.com/hapijs/joi/blob/v10.2.2/API.md#extendextension Hapi Joi Extension} + * @private + */ + +/** + * Joi `validate` method. + * @callback validate + * @memberof external:joi + * @see {@link https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback Joi.validate} + * @private + */ + +// ///////////////////////////////////////////////////////////////////////////// +// INTERNAL DEFINITIONS +// ///////////////////////////////////////////////////////////////////////////// + +/** + * The configuration properties provided to the `read` function. + * @typedef {object} ReadOptions + * @property {(string|Stream.Readable|object)} src - The source (if `string` type it is treated as a file path). + * @property {string} [origin=yaml] - The source origin type. + * @property {string} [imports=undefined] - The exports name for reading from JS source files or objects only. + * @public + */ + +/** + * The configuration properties provided to the `write` function. + * @typedef {object} WriteOptions + * @property {(string|Stream.Writable|object)} dest - The destination (if `string` type it is treated as a file path). + * @property {string} [target=js] - The destination target type. + * @property {number} [indent=2] - The indentation value for pretty-print of output. + * @property {string} [exports=undefined] - The exports name for usage in JS destination files only. + * @property {boolean} [force=false] - Force overwriting of existing output files on write phase. + * @property {boolean} [no-es6=false] - Whether not to use ECMAScript6 syntax for JS type output like + * `module.exports` instead of `export default`, applicable only + * for JS output. + * @property {boolean} [no-single=false] - Whether _not_ to use single-quotes style for values in JS type + * output (i.e. double-quotes). + * @public + */ + +/** + * The configuration properties provided to the `transform` function. + * @typedef {object} TransformOptions + * @property {(string|Stream.Readable|object)} src - The _read_ source (if `string` type it is treated as a file + * path). + * @property {string} [origin=yaml] - The _read_ source origin type. + * @property {string} [imports=undefined] - The _read_ exports name for reading from JS source files or + * objects only. + * @property {Function} [transform] - The option is a _transformation_ function with the following + * signature: + *

+ * ``` + * [async|Promise] function(object) + * ``` + * @property {(string|Stream.Writable|object)} [dest] - The _write_ destination (if `string` type it is treated as a + * file path). This property could be optional in case we infer a + * value from `src` which is then either a string or a file stream + * where can get the file path from. If this detection process + * cannot be fulfilled then the property is `undefined` and the + * transform process will fail with a `ValidationError` on write + * phase. + * @property {string} [target=js] - The _write_ target type. + * @property {number} [indent=2] - The _write_ indentation value for pretty-print of output. + * @property {string} [exports=undefined] - The _write_ exports name for usage in JS destination files only. + * @property {boolean} [force=false] - Force overwriting of existing output files on write phase. + * @property {boolean} [no-es6=false] - Whether not to use ECMAScript6 syntax for JS type output like + * `module.exports` instead of `export default`, applicable only + * for JS output. + * @property {boolean} [no-single=false] - Whether _not_ to use single-quotes style for values in JS type + * output (i.e. double-quotes). + * @public + */ diff --git a/src/validation/joi-extensions-file-utils.js b/src/validation/joi-extensions-file-utils.js new file mode 100644 index 0000000..4b00f24 --- /dev/null +++ b/src/validation/joi-extensions-file-utils.js @@ -0,0 +1,25 @@ +import fs from 'fs'; +import path from 'path'; + +/** + * @module jy-transform:validation:joi-extensions-file-utils + * @description An (extended) Joi validation schema helper functions for the module options on FS validation. + * @private + */ + +/** + * Checks if given `pathStr` is an existing file after resolving `pathStr` relative to CWD. + * + * @param {string} pathStr - The string to check for being a file. + * @returns {boolean} Value `true` if it is a file and exists, else `false`. + * @protected + */ +export function isExistingFile(pathStr) { + const filePath = path.resolve(pathStr); + try { + const stats = fs.statSync(filePath); + return stats.isFile(); + } catch (err) { + return false; + } +} diff --git a/src/validation/joi-extensions-identifier-utils.js b/src/validation/joi-extensions-identifier-utils.js new file mode 100644 index 0000000..7fd3d80 --- /dev/null +++ b/src/validation/joi-extensions-identifier-utils.js @@ -0,0 +1,32 @@ +/** + * @module jy-transform:validation:joi-extensions-identifier-utils + * @description An (extended) Joi validation schema helper function for the module options to validate ES6 identifiers. + * @private + */ + +/** + * Created at [Generating a regular expression to match valid JavaScript identifiers] + * (https://mathiasbynens.be/demo/javascript-identifier-regex). + * @type {RegExp} + * @private + */ +// eslint-disable-next-line max-len, no-useless-escape +const identifierRegExpECMAScript6 = /^(?!(?:do|if|in|for|let|new|try|var|case|else|enum|eval|null|this|true|void|with|await|break|catch|class|const|false|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$)(?:[\$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D])(?:[\$0-9A-Z_a-z\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B4\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF])*$/; + +/** + * This method checks if a given `identifier` is a valid ECMAScript 6 identifier. + * + * @param {string} identifier - The identifier to check. + * @returns {boolean} A `true` if valid, else `false`. + * @protected + * @example + * import { isValidEs6Identifier } from './validation/joi-extensions-identifier-utils.js'; + * const identifier = 'foo'; + * console.log('valid = ' + isValidEs6Identifier(identifier)); + */ +export function isValidEs6Identifier(identifier) { + if (typeof identifier !== 'string') { + return false; + } + return identifierRegExpECMAScript6.test(identifier); +} diff --git a/src/validation/joi-extensions.js b/src/validation/joi-extensions.js new file mode 100644 index 0000000..2134f00 --- /dev/null +++ b/src/validation/joi-extensions.js @@ -0,0 +1,50 @@ +import JoiBase from 'joi'; +import { isExistingFile } from './joi-extensions-file-utils'; +import { isValidEs6Identifier } from './joi-extensions-identifier-utils'; + +/** + * @module jy-transform:validation:joi-extension + * @description The module exporting the {@link external:joi.Extension}s for option validations. + * @see {@link https://github.com/hapijs/joi/blob/v10.2.2/API.md#extendextension Joi.extend(extension) method}. + * @private + */ + +/** + * The common {@link external:joi.Schema} validation extensions: + * - `existingFile` - needs to be an absolute or relative path to an existing file. + * - `validEs6Identifier` - needs to be a valid ECMAScript 6 identifier. + * @type {external:joi.Extension} + * @private + */ +export const EXTENSIONS = { + base: JoiBase.string(), + name: 'string', + language: { + existingFile: 'needs to be an absolute or relative path to an existing file (given file path: {{v}})', + validEs6Identifier: 'needs to be a valid ECMAScript 6 identifier (given identifier: {{v}})' + }, + rules: [ + { + name: 'existingFile', + validate(params, value, state, options) { + if (isExistingFile(value)) { + return value; // Everything is OK + } + // Generate an error, state and options need to be passed + return this.createError('string.existingFile', { v: value }, state, options); + } + }, + { + name: 'validEs6Identifier', + validate(params, value, state, options) { + if (isValidEs6Identifier(value)) { + return value; // Everything is OK + } + // Generate an error, state and options need to be passed + return this.createError('string.validEs6Identifier', { v: value }, state, options); + } + } + ] +}; + +export default JoiBase.extend(EXTENSIONS); diff --git a/src/validation/options-schema-utils.js b/src/validation/options-schema-utils.js new file mode 100644 index 0000000..d0f7de2 --- /dev/null +++ b/src/validation/options-schema-utils.js @@ -0,0 +1,138 @@ +import path from 'path'; +import isStream from 'is-stream'; +import { + EXT_TO_TYPE_MAP, + DEFAULT_ORIGIN, + DEFAULT_TARGET, +} from '../constants'; + +/** + * @module jy-transform:validation:options-schema-utils + * @description Provides some helper functions used in {@link module:validation:options-schema} to resolve default + * values for origin and target depending on the `options.src` or `options.dest` value. + * @type {Object} + * @see {@link module:validation:options-schema} + * @private + */ + + +/** + * Checks if passed `object` is a file stream instance. + * + * @param {*} object - The object to check. + * @returns {boolean} A `true` if passed `object` is a file stream instance, else `false`. + * @private + */ +const isFileStream = (object) => { + return isStream(object) && object.path; +}; + +/** + * Returns the passes `dest` value or an adapted destination path (the latter if `target` is defined an differs from + * destinations path extension). + * + * @param {string} dest - The destination path. + * @param {string} [target] - The target file type of destination. + * @returns {string} The `dest` value or an adapted destination path. + * @private + */ +const adaptTargetPathType = (dest, target) => { + if (target) { + const tmpDest = dest; + const destDirName = path.dirname(tmpDest); + const ext = path.extname(tmpDest); + const basename = path.basename(tmpDest, ext); + let destType = ext; + if (ext.charAt(0) === '.') { + destType = ext.substr(1); + } + if (EXT_TO_TYPE_MAP[destType] !== target) { + destType = target; + } + return path.join(destDirName, basename + '.' + destType); + } + return dest; +}; + +/** + * This function is used to infer a _default_ value in case `options.dest` is not defined. + * Checks if `context.src` is either a string or a file stream where can get the file path from. + * If this detection process cannot be fulfilled (i.e. we cannot infer from options.src `Object` + * type or a `Readable` type which is not a _file_ stream) then the function returns `undefined`. + * + * @param {Object} context - The validation context. + * @returns {string|undefined} The adapted `dest` path if possible, or `undefined`. + */ +export const inferDestDefaultFromSrc = (context) => { + if (typeof context.src === 'string' || isFileStream(context.src)) { + return adaptTargetPathType((context.src.path || context.src), context.target); + } + return undefined; +}; + +/** + * Infer from path extension to a type using default value as fallback. + * + * @param {string} pathStr - The file path with or without extension. + * @param {string} defaultValue - The default value to use if type cannot be inferred from path. + * @returns {string} A type value. + * @private + */ +const getTypeFromFilePath = (pathStr, defaultValue) => { + let type; + if (typeof pathStr === 'string') { // this is needed since node.js versions < v6 do not support throwing a TypeError! + let ext = path.extname(pathStr); + if (ext.charAt(0) === '.') { + ext = ext.substr(1); + } + type = EXT_TO_TYPE_MAP[ext]; + } + if (!type) { + type = defaultValue; + } + return type; +}; + +/** + * Infers the _origin_ type value from current validation context. + * + * @param {Object} context - The validation context. + * @returns {string} The target type. + * @protected + */ +export const inferOriginDefault = (context) => { + let type; + if (isStream.readable(context.src)) { + type = getTypeFromFilePath(context.src.path, DEFAULT_ORIGIN); + } else if (typeof context.src === 'string') { + type = getTypeFromFilePath(context.src, DEFAULT_ORIGIN); + } else { + type = DEFAULT_ORIGIN; + } + return type; +}; + +/** + * Infers the _target_ type value from current validation context. + * + * @param {Object} context - The validation context. + * @returns {string} The target type. + * @protected + */ +export const inferTargetDefault = (context) => { + let type; + if (isStream.writable(context.dest)) { + type = getTypeFromFilePath(context.dest.path, DEFAULT_TARGET); + } else if (typeof context.dest === 'string') { + type = getTypeFromFilePath(context.dest, DEFAULT_TARGET); + } else { + type = DEFAULT_TARGET; + } + return type; +}; + +export default { + inferOriginDefault, + inferTargetDefault, + inferDestDefaultFromSrc, +}; diff --git a/src/validation/options-schema.js b/src/validation/options-schema.js new file mode 100644 index 0000000..27e5113 --- /dev/null +++ b/src/validation/options-schema.js @@ -0,0 +1,241 @@ +import { Stream } from 'stream'; +import Joi from './joi-extensions'; +import { + inferDestDefaultFromSrc, + inferOriginDefault, + inferTargetDefault, +} from './options-schema-utils'; +import { + TYPE_YAML, + TYPE_JS, + TYPE_JSON, + DEFAULT_FORCE_FILE_OVERWRITE, + DEFAULT_INDENT, + MIN_INDENT, + MIN_YAML_INDENT, + MAX_INDENT, + DEFAULT_STRICT, + DEFAULT_ES5, + DEFAULT_DOUBLE_QUOTES, +} from '../constants'; + +/** + * @module jy-transform:validation:options-schema + * @description The module options schema used in {@link module:options-validator}. + * @type {Object} + * @see {@link module:options-validator} + * @private + */ + +/** + * The `force` option schema. + * @type {external:joi.Schema} + * @private + */ +const forceSchema = Joi + .boolean() + .default(DEFAULT_FORCE_FILE_OVERWRITE) + .description('Force overwriting of existing output files on write phase.'); + +/** + * The `force` option schema. + * @type {external:joi.Schema} + * @private + */ +const strictSchema = Joi + .boolean() + .default(DEFAULT_STRICT) + .description('Whether to write a "use strict;" in JS type output.'); + +/** + * The `es5` option schema. + * @type {external:joi.Schema} + * @private + */ +const es5Schema = Joi + .boolean() + .default(DEFAULT_ES5) + .description('Whether not to use ECMAScript6 syntax for JS type output like "module.exports" instead of ' + + '"export default", applicable only for JS output.'); + +/** + * The `double` option schema. + * @type {external:joi.Schema} + * @private + */ +const doubleSchema = Joi + .boolean() + .default(DEFAULT_DOUBLE_QUOTES) + .description('Whether not to use single-quotes style for values in JS type output (i.e. double-quotes).'); + +/** + * The `indent` option schema. + * @type {external:joi.Schema} + * @private + */ +const indentSchema = Joi.when('target', { + is: TYPE_YAML, + then: Joi + .number() + .integer() + .min(MIN_YAML_INDENT) // Must be 2 for YAML type! + .max(MAX_INDENT) + .default(DEFAULT_INDENT), + otherwise: Joi + .number() + .integer() + .min(MIN_INDENT) + .max(MAX_INDENT) + .default(DEFAULT_INDENT), +}).description('The indention value for pretty-print of output.'); + +/** + * The `exports` option schema. + * @type {external:joi.Schema} + * @private + */ +const exportsSchema = Joi + .string() + .validEs6Identifier() + .description('The name of property to export while writing a JS object to a JS output destination.'); + +/** + * The `target` option schema. + * @type {external:joi.Schema} + * @private + */ +const targetSchema = Joi.when('dest', { + is: Joi.object().type(Stream.Writable), + then: Joi + .string() + .valid(TYPE_YAML, TYPE_JSON, TYPE_JS) + .default(inferTargetDefault, 'try target default resolution from dest type if not set (Stream.Writable)'), + otherwise: Joi + .when('dest', { + is: Joi.string(), + then: Joi + .string() + .valid(TYPE_YAML, TYPE_JSON, TYPE_JS) + .default(inferTargetDefault, 'try target resolution from dest type if latter not set (String)'), + otherwise: Joi // check + .string() + .valid(TYPE_YAML, TYPE_JSON, TYPE_JS) + .default(TYPE_JS), + }), +}).description('The target type of output.'); + +/** + * The prepared {@link external:joi.Schema} for validating the {@link ReadOptions}. + * @type {external:joi.Schema} + * @constant + * @private + */ +export const readOptionsSchema = Joi.object().keys({ + src: Joi + .alternatives().try( + Joi.string() + .min(1) + .label('src - INPUT-FILE') + .existingFile(), + Joi.object().type(Stream.Readable), + Joi.object().type(Object), + ) + .required() + .description('The input source (if string type is treated as a file path).'), + origin: Joi + .when('src', { + is: Joi.object().type(Stream.Readable), + then: Joi + .string() + .valid(TYPE_YAML, TYPE_JSON, TYPE_JS) + .default(inferOriginDefault, 'try origin resolution from src type if not set (Stream.Readable)'), + otherwise: Joi + .when('src', { + is: Joi.string(), + then: Joi + .string() + .valid(TYPE_YAML, TYPE_JSON, TYPE_JS) + .default(inferOriginDefault, 'try origin resolution from src type if latter not set (String)'), + otherwise: Joi // else could only be JS Object + .string() + .valid(TYPE_JS) + .default(TYPE_JS) + }), + }) + .description('The origin type of input.'), + imports: Joi + .string() + .validEs6Identifier() + .description('The name of property to import while reading a JS input source.'), +}).default() + .required() + .unknown(); + +/** + * The prepared {@link external:joi.Schema} for validating the {@link WriteOptions}. + * @type {external:joi.Schema} + * @constant + * @private + */ +export const writeOptionsSchema = Joi.object().keys({ + dest: Joi + .alternatives().try( + Joi.string() + .min(1) + .label('dest - OUTPUT-FILE'), + Joi.object().type(Stream.Writable), + Joi.object().type(Object), + ) + .required() + .description('The output destination (if string type is treated as a file path).'), + target: targetSchema, + exports: exportsSchema, + indent: indentSchema, + force: forceSchema, + strict: strictSchema, + es5: es5Schema, + double: doubleSchema, +}).default() + .required() + .unknown(); + +/** + * The prepared {@link external:joi.Schema} for validating the {@link TransformOptions}. + * @type {external:joi.Schema} + * @constant + * @private + */ +export const transformOptionsSchema = readOptionsSchema.concat(Joi + .object() + .keys({ + transform: Joi + .func() + .arity(1) + .default(object => object) + .description('The transformation function to alter a read object.'), + dest: Joi + .alternatives().try( + Joi.string() + .min(1) + .label('dest - OUTPUT-FILE'), + Joi.object().type(Stream.Writable), + Joi.object().type(Object), + ) + .default(inferDestDefaultFromSrc, 'try dest resolution from src if not set') + .description('The output destination (if string type is treated as a file path).'), + target: targetSchema, + exports: exportsSchema, + indent: indentSchema, + force: forceSchema, + strict: strictSchema, + es5: es5Schema, + double: doubleSchema, + }).default() + .required() +); + +export default { + readOptionsSchema, + writeOptionsSchema, + transformOptionsSchema, +}; diff --git a/src/writer.js b/src/writer.js new file mode 100644 index 0000000..d99de2b --- /dev/null +++ b/src/writer.js @@ -0,0 +1,141 @@ +import isStream from 'is-stream'; +import jsYaml from 'js-yaml'; +import { + TYPE_JS, + TYPE_JSON, + TYPE_YAML, +} from './constants'; +import Joi from './validation/joi-extensions'; +import { writeOptionsSchema } from './validation/options-schema'; +import { + serializeJsToJsonString, + serializeJsToString +} from './serialize-utils'; +import { + writeToFile, + writeToStreamWritable, +} from './io-utils'; + +/** + * @module jy-transform:writer + * @description This module provides the _public_ interface for the _write_ functionality to write JS objects from + * memory to a JSON/JS/YAML destination (file, `Object` or {@link stream.Writable}). + * @private + */ + +// //////////////////////////////////////////////////////////////////////////// +// METHODS (PRIVATE) +// //////////////////////////////////////////////////////////////////////////// + +/** + * Writes a JS object to a YAML destination. + * + * @param {Object} object - The JS object to write into YAML destination. + * @param {WriteOptions} options - The write destination and indention. + * @see {@link MIN_INDENT} + * @see {@link DEFAULT_INDENT} + * @see {@link MAX_INDENT} + * @returns {Promise.} Containing the write success message to handle by caller (e.g. for logging). + * @throws {Error} If YAML destination could not be written due to any reason. + * @private + */ +async function writeYaml(object, options) { + let yaml; + try { + yaml = jsYaml.safeDump(object, { indent: options.indent, noRefs: true }); + } catch (err) { + err.message = 'Could not write YAML to \'' + options.dest + '\', cause: ' + err.message; + throw err; + } + + if (typeof options.dest === 'string') { // file + return writeToFile(yaml, options.dest, TYPE_YAML, options.force); + } else if (isStream.writable(options.dest)) { // stream.Writable | stream.Duplex + return writeToStreamWritable(yaml, options.dest, TYPE_YAML); + } + // object + options.dest = yaml; + return 'Writing serialized YAML to options.dest successful.'; +} + +/** + * Writes a JS object to a JSON destination. + * + * @param {Object} object - The JS object to write into JSON destination. + * @param {WriteOptions} options - The write destination and indention. + * @see {@link MIN_INDENT} + * @see {@link DEFAULT_INDENT} + * @see {@link MAX_INDENT} + * @returns {Promise.} Containing the write success message to handle by caller (e.g. for logging). + * @private + */ +async function writeJson(object, options) { + const jsonString = await serializeJsToJsonString(object, options.indent); + if (typeof options.dest === 'string') { // file + return writeToFile(jsonString, options.dest, TYPE_JSON, options.force); + } else if (isStream.writable(options.dest)) { // stream + return writeToStreamWritable(jsonString, options.dest, TYPE_JSON); + } + // object + options.dest = jsonString; + return 'Writing JSON to options.dest successful.'; +} + +/** + * Writes a JS object to a JS destination. + * + * @param {Object} object - The JSON to write into JS destination. + * @param {WriteOptions} options - The write options. + * @see {@link MIN_INDENT} + * @see {@link DEFAULT_INDENT} + * @see {@link MAX_INDENT} + * @returns {Promise.} Containing the write success message to handle by caller (e.g. for logging). + * @private + */ +async function writeJs(object, options) { + const data = await serializeJsToString(object, options); + if (typeof options.dest === 'string') { // file + return writeToFile(data, options.dest, TYPE_JS, options.force); + } else if (isStream.writable(options.dest)) { // stream + return writeToStreamWritable(data, options.dest, TYPE_JS); + } + // object + let msg; + if (options.exports) { + options.dest[options.exports] = object; + msg = 'Writing JS to options.dest.' + options.exports + ' successful.'; + } else { + Object.assign(options.dest, object); + msg = 'Writing JS to options.dest successful.'; + } + return msg; +} + +/** + * Writes the passed JS object to a particular destination described by the passed `options`. + * + * @param {Object} object - The JS source object to write. + * @param {WriteOptions} options - The write options. + * @returns {Promise.} The result message. + * @resolve {string} With the write success message. + * @reject {Error} If any write error occurs. + * @reject {ValidationError} If any `options` validation occurs. + * @public + */ +export async function write(object, options) { + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + // HINT: we have to use the original options object because the caller must not loose the reference to options.dest! + validatedOptions.dest = options.dest; + switch (validatedOptions.target) { + case TYPE_JSON: + return writeJson(object, options); + case TYPE_YAML: + return writeYaml(object, options); + default: + return writeJs(object, options); + } +} + +export default { + write, +}; diff --git a/test/data/test-data b/test/data/test-data new file mode 100644 index 0000000..d3cac0e --- /dev/null +++ b/test/data/test-data @@ -0,0 +1,3 @@ +module.exports = { + myproperty: "old value" +}; diff --git a/test/data/test-data-cli.js b/test/data/test-data-cli.js new file mode 100644 index 0000000..875c5ee --- /dev/null +++ b/test/data/test-data-cli.js @@ -0,0 +1,3 @@ +module.exports = { + foo: 'bar' +}; diff --git a/test/data/test-data-cli.json b/test/data/test-data-cli.json new file mode 100644 index 0000000..e63d37b --- /dev/null +++ b/test/data/test-data-cli.json @@ -0,0 +1,3 @@ +{ + "foo": "bar" +} diff --git a/test/data/test-data-cli.yaml b/test/data/test-data-cli.yaml new file mode 100644 index 0000000..20e9ff3 --- /dev/null +++ b/test/data/test-data-cli.yaml @@ -0,0 +1 @@ +foo: bar diff --git a/test/data/test-data-json b/test/data/test-data-json new file mode 100644 index 0000000..46b2aae --- /dev/null +++ b/test/data/test-data-json @@ -0,0 +1,3 @@ +{ + "myproperty": "old value" +} diff --git a/test/data/test-data-yaml b/test/data/test-data-yaml new file mode 100644 index 0000000..f2bc229 --- /dev/null +++ b/test/data/test-data-yaml @@ -0,0 +1 @@ +myproperty: old value diff --git a/test/data/writable-test-dummy.txt b/test/data/writable-test-dummy.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/data/writable-test-dummy.yaml b/test/data/writable-test-dummy.yaml new file mode 100644 index 0000000..e69de29 diff --git a/test/functional/test-jyt-cli.js b/test/functional/test-jyt-cli.js new file mode 100644 index 0000000..b92bae5 --- /dev/null +++ b/test/functional/test-jyt-cli.js @@ -0,0 +1,296 @@ +import jsYaml from 'js-yaml'; +import promisify from 'promisify-es6'; +import { exec } from 'child_process'; +import fsExtra from 'fs-extra'; +import fs from 'fs'; +import cwd from 'cwd'; +import path from 'path'; +import { logger } from '../logger'; +import { + UTF8, + TYPE_JS, +} from '../../src/constants'; +import { + TEST_SUITE_DESCRIPTION_FUNCTIONAL, + TEST_DATA_DIR, + EXPECTED_VALUE, +} from '../helper-constants'; + +const fsPromised = promisify(fs); + +/** + * @module jy-transform:unit-test:test-cli + * @description This unit test suite checks the correct transformation behaviour of the CLI interface. + * @private + */ + +/** + * Temporary base dir for transformer test output. + * @type {string} + * @constant + * @private + */ +const CLI_TEST_BASE_DIR = './test/functional/tmp/cli'; + +/** + * A YAML source file path. + * @type {string} + * @constant + * @private + */ +const SRC_YAML = TEST_DATA_DIR + '/test-data-cli.yaml'; + +/** + * A JSON source file path. + * @type {string} + * @constant + * @private + */ +const SRC_JSON = TEST_DATA_DIR + '/test-data-cli.json'; + +/** + * A JS source file path. + * @type {string} + * @constant + * @private + */ +const SRC_JS = TEST_DATA_DIR + '/test-data-cli.js'; + +/** + * Object mapping from JS option to to short CLI option. + * @type {{src: string, dest: string, origin: string, target: string, indent: string, force: string, imports: string, + * exports: string}} + * @private + */ +const CLI_OPTIONS_LONG_TO_SHORT_MAP = { + src: '', + dest: '', + origin: '-o', + target: '-t', + indent: '-i', + force: '-f', + imports: '-m', + exports: '-x', + strict: '-s', + es5: '--es5', // no short available + double: '--double', // no short available +}; + +/** + * Creates the options from the given source and destination path parameters. + * + * @param {string} src - The source path. + * @param {string} dest - The destination path. + * @returns {{src: string, dest: string}} The options object. + * @private + */ +function createOptions(src, dest) { + return { + src, + dest, + }; +} + +/** + * Executes `./jyt` script with given args (which includes source, destination and all options). + * + * @param {string[]} args - The source, destination CLI arguments and all CLI options. + * @returns {Promise} A result, see details. + * @resolve {string} The transformation success message. + * @reject {Error} Any error occurred. + * @private + */ +function execJyt(args) { + return new Promise((resolve, reject) => { + const command = './jyt ' + args.join(' '); + logger.info('executing command: ' + command); + const childProcess = exec(command, { cwd: cwd() /* , encoding: 'utf8' */ }, (err, stdout, stderr) => { + if (err) { + logger.error(`err: ${err}`); + reject(err); + return; + } + logger.debug(`stdout: ${stdout}`); + logger.debug(`stderr: ${stderr}`); + resolve(stdout || stderr); + }); + childProcess.on('error', logger.error); + childProcess.on('exit', (code, signal) => + logger.debug(`child process exited with code ${code} and signal ${signal}`)); + }); +} + +/** + * Creates the CLI args/options from given `options` object. + * + * @param {TransformOptions} options - The transformation options. + * @returns {string[]} The CLI args and options. + * @private + */ +function optionsToArgs(options) { + const keys = Object.keys(options); + return keys.map(key => `${CLI_OPTIONS_LONG_TO_SHORT_MAP[key]} ${options[key]}`); +} + +/** + * Helper method which asserts the successful transformation. + * + * @param {TransformOptions} options - The transformation options. + * @param {boolean} [es6=true] - Whether to use ES6 syntax. + * @private + */ +async function assertTransformSuccess(options, es6 = true) { + expect.assertions(2); + const msg = await execJyt(optionsToArgs(options)); + logger.debug(msg); + const stats = fsExtra.statSync(options.dest); + expect(stats.isFile()).toBeTruthy(); + // eslint-disable-next-line import/no-dynamic-require, global-require + const json = es6 ? require(path.resolve(options.dest)).default : require(path.resolve(options.dest)); + expect(json.foo).toBe(EXPECTED_VALUE); +} + +/** + * Helper method which asserts the successful transformation. + * + * @param {Object} options - The transformation options. + */ +async function assertYamlTransformSuccess(options) { + expect.assertions(3); + const msg = await execJyt(optionsToArgs(options)); + logger.debug(msg); + expect(msg).toEqual(expect.any(String)); + const stats = fsExtra.statSync(options.dest); + expect(stats.isFile()).toBeTruthy(); + const yaml = await fsPromised.readFile(options.dest, UTF8); + const object = jsYaml.safeLoad(yaml); + expect(object.foo).toBe(EXPECTED_VALUE); +} + +describe(TEST_SUITE_DESCRIPTION_FUNCTIONAL + ' - ./jyt -> ./src/cli.js - ', () => { + beforeAll(() => { + fsExtra.ensureDirSync(CLI_TEST_BASE_DIR); + fsExtra.emptyDirSync(CLI_TEST_BASE_DIR); + }); + + describe('Testing CLI transforming from YAML to JS to relative path', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data.js'; + const DEST_NO_ES6 = CLI_TEST_BASE_DIR + '/test-data-no-es6.js'; + + beforeAll(async () => { + // Prepare test data. + try { + fsExtra.copySync(SRC_YAML, CLI_TEST_BASE_DIR + '/test-data.yaml'); + logger.debug('copied ' + SRC_YAML + ' to ' + CLI_TEST_BASE_DIR + '/test-data.yaml'); + } catch (err) { + logger.error('could not copy ' + SRC_YAML + ' to ' + CLI_TEST_BASE_DIR + '/test-data.yaml: ' + + err.stack); + throw err; + } + }); + + it('should store ' + DEST + ' file relative to ' + CLI_TEST_BASE_DIR + '/test-data.yaml', async () => { + expect.assertions(2); + const msg = await execJyt(optionsToArgs({ + src: path.resolve(CLI_TEST_BASE_DIR + '/test-data.yaml'), + target: TYPE_JS, + })); + logger.debug(msg); + const stats = fs.statSync(DEST); + logger.debug('STATS: ' + JSON.stringify(stats, null, 4)); + expect(stats.isFile()).toBe(true); + // eslint-disable-next-line import/no-unresolved, global-require, import/no-dynamic-require + const result = require('./tmp/cli/test-data.js').default; + expect(result.foo).toBe(EXPECTED_VALUE); + }); + + it('should store ' + CLI_TEST_BASE_DIR + '/test-data-no-es6.js file relative to ' + + CLI_TEST_BASE_DIR + '/test-data.yaml (non-ES6 syntax)', async () => { + expect.assertions(2); + const msg = await execJyt(optionsToArgs({ + src: path.resolve(CLI_TEST_BASE_DIR + '/test-data.yaml'), + dest: path.resolve(DEST_NO_ES6), + target: TYPE_JS, + es5: true, + })); + logger.debug(msg); + const stats = fs.statSync(DEST_NO_ES6); + expect(stats.isFile()).toBe(true); + // eslint-disable-next-line import/no-unresolved, global-require, import/no-dynamic-require + const result = require('./tmp/cli/test-data-no-es6.js'); + expect(result.foo).toBe(EXPECTED_VALUE); + }); + }); + + describe('Testing CLI transforming from YAML to JS', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-yaml-js.js'; + + it('should store ' + SRC_YAML + ' file to ' + DEST, () => + assertTransformSuccess(createOptions(SRC_YAML, DEST)) + ); + }); + + describe('Testing CLI transforming from YAML to JSON', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-yaml-json.json'; + + it('should store ' + SRC_YAML + ' file to ' + DEST, () => + assertTransformSuccess(createOptions(SRC_YAML, DEST), false) + ); + }); + + describe('Testing CLI transforming from JSON to JS', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-json-js.js'; + + it('should store ' + SRC_JSON + ' file to ' + DEST, () => + assertTransformSuccess(createOptions(SRC_JSON, DEST)) + ); + }); + + describe('Testing CLI transforming from JS to JSON', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-js-json.json'; + + it('should store ' + SRC_JS + ' file to ' + DEST, () => + assertTransformSuccess(createOptions(SRC_JS, DEST), false) + ); + }); + + describe('Testing CLI transforming from JS to YAML', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-js-yaml.yaml'; + + it('should store ' + SRC_JS + ' file to ' + DEST, () => + assertYamlTransformSuccess(createOptions(SRC_JS, DEST)) + ); + }); + + describe('Testing CLI transforming from YAML to YAML', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-yaml-yaml.yaml'; + + it('should store ' + SRC_YAML + ' file to ' + DEST, () => + assertYamlTransformSuccess(createOptions(SRC_YAML, DEST)) + ); + }); + + describe('Testing CLI transforming from JSON to JSON', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-json-json.json'; + + it('should store ' + SRC_JS + ' file to ' + DEST, () => + assertTransformSuccess(createOptions(SRC_JS, DEST), false) + ); + }); + + describe('Testing CLI transforming from JSON to YAML', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-json-yaml.yaml'; + + it('should store ' + SRC_JSON + ' file to ' + DEST, () => + assertYamlTransformSuccess(createOptions(SRC_JSON, DEST)) + ); + }); + + describe('Testing CLI transforming from JS to JS', () => { + const DEST = CLI_TEST_BASE_DIR + '/test-data-transform-js-js.js'; + + it('should store ' + SRC_JS + ' file to ' + DEST, () => + assertTransformSuccess(createOptions(SRC_JS, DEST)) + ); + }); +}); diff --git a/test/functional/test-reader.js b/test/functional/test-reader.js new file mode 100644 index 0000000..2b77186 --- /dev/null +++ b/test/functional/test-reader.js @@ -0,0 +1,289 @@ +import YAMLException from 'js-yaml/lib/js-yaml/exception'; +import fs from 'fs'; +import { read } from '../../src/reader'; +import { TEST_SUITE_DESCRIPTION_FUNCTIONAL } from '../helper-constants'; +import { + TYPE_JS, + TYPE_YAML, + TYPE_JSON, +} from '../../src/constants'; + +/** + * @module jy-transform:unit-test:test-reader + * @description This unit test suite checks the validity and correctness of the Reader module. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_FUNCTIONAL + ' - reader - ', () => { + /** + * Assert a `Error` properties for a given reader function. + * + * @param {Object} options - The options which potentially produce the error. + * @param {string} [match=Error] - The error name to match. + * @private + */ + const expectReaderError = async (options, match = 'Error') => { + expect.assertions(1); + try { + await read(options); + } catch (err) { + expect(err.name).toMatch(match); + } + }; + + /** + * Assert an `Error` for a given reader function. + * + * @param {Object} options - The options which potentially produce the error. + * @param {Error} [match=Error'] - The error type to match. + * @private + */ + const expectReaderErrorByType = (options, match = Error) => { + expect.assertions(1); + return expect(read(options)).rejects.toBeInstanceOf(match); + }; + + /** + * Assert a successful reading of input. + * + * @param {Object} options - The options. + * @param {string} key - The function to call for assertion. + * @param {*} expectedValue - The expected value for `key`. + * @private + */ + const expectReaderSuccess = async (options, key, expectedValue) => { + expect.assertions(2); + const json = await read(options); + expect(json).toBeDefined(); + expect(json[key]).toBe(expectedValue); + }; + + describe('The reading from JS', () => { + const exports = 'fooBar'; + const exportsNotExists = 'notFooBar'; + const invalidIdentifier = '#3/-'; + + it('should reject with options.imports == \'\'', () => { + const options = { + src: './test/data/test-data.js', + imports: '', + }; + return expectReaderError(options, 'ValidationError'); + }); + + it('should read JS successfully', () => + expectReaderSuccess({ src: { myproperty: 'value' } }, 'myproperty', 'value') + ); + + it('should read JS from file successfully', () => + expectReaderSuccess({ src: './test/data/test-data.js' }, 'myproperty', 'old value') + ); + + it('should read JS from JS object successfully and both object references are different', async () => { + expect.assertions(3); + const options = { + src: {}, + }; + const object = await read(options); + expect(object).toBeDefined(); + expect(object).toMatchObject({}); + expect(object === options.src).toBe(false); + }); + + it('should read JS from file with options.imports == \'' + exports + '\' successfully', async () => { + expect.assertions(5); + const options = { + src: './test/data/test-imports.js', + imports: exports, + }; + const json = await read(options); + expect(json).toBeDefined(); + expect(Object.prototype.hasOwnProperty.call(json, exports)).toBeFalsy(); + expect(Object.prototype.hasOwnProperty.call(json, 'bar')).toBeFalsy(); + expect(Object.prototype.hasOwnProperty.call(json, 'foo')).toBeTruthy(); + expect(json.foo).toBe('bar'); + }); + + it('should read JS from file with options.imports == \'' + exports + + '\' and given origin for unsupported file extension successfully', async () => { + expect.assertions(5); + const options = { + src: './test/data/test-imports.txt', + imports: exports, + origin: TYPE_JS, + }; + const json = await read(options); + expect(json).toBeDefined(); + expect(Object.prototype.hasOwnProperty.call(json, exports)).toBeFalsy(); + expect(Object.prototype.hasOwnProperty.call(json, 'bar')).toBeFalsy(); + expect(Object.prototype.hasOwnProperty.call(json, 'foo')).toBeTruthy(); + expect(json.foo).toBe('bar'); + }); + + it('should reject reading JS from file with Error on invalid identifier for options.imports: ' + + invalidIdentifier, () => { + const options = { + src: './test/data/test-imports.js', + imports: invalidIdentifier, + }; + return expectReaderError(options, 'ValidationError'); + }); + + it('should reject reading JS from file with Error on non-existent identifier for options.imports: ' + + exportsNotExists, () => { + const options = { + src: './test/data/test-imports.js', + imports: exportsNotExists, + }; + return expectReaderErrorByType(options, Error); + }); + + it('should read JSON from file successfully', () => + expectReaderSuccess({ src: './test/data/test-data.json' }, 'myproperty', 'old value') + ); + + it('should read JS from object successfully', async () => { + const options = { + src: { + foo: 'bar', + }, + }; + await expectReaderSuccess(options, 'foo', 'bar'); + }); + + it('should read JS from Object with options.imports == \'' + exports + '\' successfully', async () => { + expect.assertions(6); + const options = { + src: { + fooBar: { + bar: 'foo', + foo: 'bar', + }, + }, + imports: exports, + }; + const json = await read(options); + expect(json).toBeDefined(); + expect(Object.prototype.hasOwnProperty.call(json, exports)).toBeFalsy(); + expect(Object.prototype.hasOwnProperty.call(json, 'bar')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(json, 'foo')).toBeTruthy(); + expect(json.bar).toBe('foo'); + expect(json.foo).toBe('bar'); + }); + + it('should reject reading JS from Object with Error on invalid identifier for options.imports: ' + + invalidIdentifier, () => { + const options = { + src: { + fooBar: { + bar: 'foo', + foo: 'bar', + }, + }, + imports: invalidIdentifier, + }; + return expectReaderError(options, 'ValidationError'); + }); + + it('should reject reading JS (deeply) from file with Error on non-existent identifier for options.imports: ' + + exportsNotExists, () => { + const options = { + src: { + fooBar: { + bar: 'foo', + foo: 'bar', + }, + }, + imports: exportsNotExists, + }; + return expectReaderErrorByType(options, Error); + }); + + it('should read JSON from stream successfully', () => + expectReaderSuccess({ + origin: TYPE_JSON, + src: fs.createReadStream('./test/data/test-data.json') + }, 'myproperty', 'old value') + ); + + it('should read corrupted JSON from file path and fail by SyntaxError', () => + expectReaderErrorByType({ + origin: TYPE_JSON, + src: './test/data/test-data-corrupted.json' + }, SyntaxError) + ); + + it('should read invalid JSON from file path and fail by SyntaxError', () => + expectReaderErrorByType({ + origin: TYPE_JSON, + src: './test/data/test-data-wrong-syntax.json' + }, SyntaxError) + ); + + it('should read corrupted JSON from stream and fail by SyntaxError', () => + expectReaderErrorByType({ + origin: TYPE_JSON, + src: fs.createReadStream('./test/data/test-data-corrupted.json'), + }, SyntaxError) + ); + + it('should read invalid JSON from stream and fail by SyntaxError', () => + expectReaderErrorByType({ + origin: TYPE_JSON, + src: fs.createReadStream('./test/data/test-data-wrong-syntax.json') + }, SyntaxError) + ); + + it('should fail JS(ON) read by missing options', () => + expectReaderError(null, 'ValidationError') + ); + + it('should fail JS(ON) read by missing options.src', () => + expectReaderError({}, 'ValidationError') + ); + }); + + describe('Testing reading from YAML', () => { + it('should read YAML from file successfully', () => + expectReaderSuccess({ src: './test/data/test-data.yaml' }, 'myproperty', 'old value') + ); + + it('should read YAML from stream successfully', () => + expectReaderSuccess({ + origin: TYPE_YAML, + src: fs.createReadStream('./test/data/test-data.yaml'), + }, 'myproperty', 'old value') + ); + + it('should read invalid YAML from file path and fail by YAMLException', () => + expectReaderErrorByType({ src: './test/data/test-data-wrong-syntax.yaml' }, YAMLException) + ); + + it('should read invalid YAML from stream and fail by YAMLException', () => + expectReaderErrorByType({ + origin: TYPE_YAML, + src: fs.createReadStream('./test/data/test-data-wrong-syntax.yaml'), + }, YAMLException) + ); + + it('should fail reading YAML by providing empty JS object as options.src', () => + expectReaderError({ src: {}, origin: TYPE_YAML }, 'ValidationError') + ); + + it('should fail YAML reading by missing input options', () => + expectReaderError(null, 'ValidationError') + ); + + it('should fail reading YAML by missing options.src', () => + expectReaderError({}, 'ValidationError') + ); + + it('should fail reading YAML from configured directory source', () => + expectReaderError({ src: './test/data' }, 'ValidationError') + ); + + it('should fail reading YAML from non-existing file', () => + expectReaderError({ src: './test/data/non-existing.yml' }, 'ValidationError') + ); + }); +}); diff --git a/test/functional/test-transformer.js b/test/functional/test-transformer.js new file mode 100644 index 0000000..83fe9d9 --- /dev/null +++ b/test/functional/test-transformer.js @@ -0,0 +1,361 @@ +import jsYaml from 'js-yaml'; +import promisify from 'promisify-es6'; +import fsExtra from 'fs-extra'; +import fs from 'fs'; +import path from 'path'; +import { logger } from '../logger'; +import { transform } from '../../src/transformer'; +import { + UTF8, + TYPE_JS, +} from '../../src/constants'; +import { + TEST_SUITE_DESCRIPTION_FUNCTIONAL, + TEST_DATA_DIR, + EXPECTED_VALUE, +} from '../helper-constants'; + +/** + * Promisified `fs` module. + * @private + */ +const fsPromisified = promisify(fs); + +/** + * @module jy-transform:unit-test:test-transformer + * @description This unit test suite checks the correct transformation behaviour of the Transformer module. + * @private + */ + +/** + * A YAML source file path. + * @type {string} + * @constant + * @private + */ +const SRC_YAML = TEST_DATA_DIR + '/test-file.yaml'; + +/** + * A JSON source file path. + * @type {string} + * @constant + * @private + */ +const SRC_JSON = TEST_DATA_DIR + '/test-file.json'; + +/** + * A JS source file path. + * @type {string} + * @constant + * @private + */ +const SRC_JS = TEST_DATA_DIR + '/test-file.js'; + +/** + * Temporary base dir for writer test output. + * @type {string} + * @constant + * @private + */ +const TRANSFORMER_TEST_BASE_DIR = './test/functional/tmp/transformer'; + +/** + * Transformation middleware changing value for `foo` property. + * + * @param {Object} object - To transform. + */ +async function transformFunc(object) { + object.foo = EXPECTED_VALUE; + return object; +} + +/** + * Helper method which asserts the successful transformation. + * + * @param {Object} options - The transformation options. + * @param {boolean} [es6=true] - Whether to use ES6 syntax. + */ +async function assertTransformSuccess(options, es6 = true) { + expect.assertions(2); + const msg = await transform(options); + logger.debug(msg); + const stats = fsExtra.statSync(options.dest); + expect(stats.isFile()).toBeTruthy(); + // eslint-disable-next-line import/no-dynamic-require, global-require + const json = es6 ? require(path.resolve(options.dest)).default : require(path.resolve(options.dest)); + expect(json.foo).toBe(EXPECTED_VALUE); +} + +/** + * Helper method which asserts the successful transformation. + * + * @param {Object} options - The transformation options. + */ +async function assertYamlTransformSuccess(options) { + expect.assertions(3); + const msg = await transform(options); + logger.debug(msg); + expect(msg).toEqual(expect.any(String)); + const stats = fsExtra.statSync(options.dest); + expect(stats.isFile()).toBeTruthy(); + const yaml = await fsPromisified.readFile(options.dest, UTF8); + const object = jsYaml.safeLoad(yaml); + expect(object.foo).toBe(EXPECTED_VALUE); +} + +/** + * Creates the options from the given transform function, source and destination path parameters. + * + * @param {string} src - The source path. + * @param {string} dest - The destination path. + * @param {*} [func=transformFunc] - The transform function. + * @returns {{src: string, transform: *, dest: string}} The options object. + * @private + */ +function createOptions(src, dest, func = transformFunc) { + return { + src, + transform: func, + dest, + }; +} + +describe(TEST_SUITE_DESCRIPTION_FUNCTIONAL + ' - transformer - ', () => { + beforeAll(() => { + fsExtra.ensureDirSync(TRANSFORMER_TEST_BASE_DIR); + fsExtra.emptyDirSync(TRANSFORMER_TEST_BASE_DIR); + }); + + describe('Testing transform with middleware', () => { + it('should throw ValidationError if middleware passed is not a function type', async () => { + expect.assertions(1); + try { + await transform(createOptions({}, {}, 'not a function')); + } catch (err) { + expect(err.name).toMatch('ValidationError'); + } + }); + + it('should throw ValidationError if options.dest is not set and cannot be resolved from options.src type', + async () => { + expect.assertions(1); + try { + await transform(createOptions({} /* We cannot infer destination from undefined src type! */)); + } catch (err) { + expect(err.name).toMatch('ValidationError'); + } + } + ); + + it('should not fail if transform callback passed is returning a Promise', () => { + expect.assertions(1); + const returningPromise = async object => object; + return expect(transform(createOptions({}, {}, returningPromise))) + .resolves.toBe('Writing JS to options.dest successful.'); + }); + + it('should not fail if transform callback passed is not returning a Promise', () => { + expect.assertions(1); + const notReturningPromise = object => object; + return expect(transform(createOptions({}, {}, notReturningPromise))) + .resolves.toBe('Writing JS to options.dest successful.'); + }); + }); + + describe('Testing transform without transform callback', () => { + it('should not fail', () => { + expect.assertions(1); + return expect(transform({ src: {}, dest: {} })).resolves.toBe('Writing JS to options.dest successful.'); + }); + }); + + describe('Testing Transformer transforming from YAML to JS to relative path', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data.js'; + const DEST_NO_ES6 = TRANSFORMER_TEST_BASE_DIR + '/test-data-no-es6.js'; + const DEST_DQ_AND_STRICT = TRANSFORMER_TEST_BASE_DIR + '/test-data-double-quotes-and-strict.js'; + + beforeAll(async () => { + // Prepare test data. + try { + fsExtra.copySync(SRC_YAML, TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml'); + logger.debug('copied ' + SRC_YAML + ' to ' + TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml'); + } catch (err) { + logger.error('could not copy ' + SRC_YAML + ' to ' + TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml: ' + + err.stack); + throw err; + } + }); + + it('should store ' + DEST + ' file relative to ' + TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml', async () => { + expect.assertions(2); + const msg = await transform({ + src: path.resolve(TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml'), + transform: transformFunc, + target: TYPE_JS, + }); + logger.debug(msg); + const stats = fs.statSync(DEST); + expect(stats.isFile()).toBe(true); + // eslint-disable-next-line import/no-unresolved, global-require, import/no-dynamic-require + const json = require('./tmp/transformer/test-data.js').default; + expect(json.foo).toBe(EXPECTED_VALUE); + }); + + it('should store ' + DEST + ' file with double-quotes and strict', async () => { + expect.assertions(2); + const msg = await transform({ + src: path.resolve(TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml'), + dest: path.resolve(DEST_DQ_AND_STRICT), + transform: transformFunc, + target: TYPE_JS, + strict: true, + double: true, + }); + logger.debug(msg); + const stats = fs.statSync(DEST_DQ_AND_STRICT); + expect(stats.isFile()).toBe(true); + // eslint-disable-next-line import/no-unresolved, global-require, import/no-dynamic-require + const jsContentString = await fsPromisified.readFile(DEST_DQ_AND_STRICT, UTF8); + expect(jsContentString.startsWith('"use strict;"')).toBe(true); + }); + + it('should store ' + TRANSFORMER_TEST_BASE_DIR + '/test-data-no-es6.js file relative to ' + + TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml (non-ES6 syntax)', async () => { + expect.assertions(2); + const msg = await transform({ + src: path.resolve(TRANSFORMER_TEST_BASE_DIR + '/test-data.yaml'), + dest: path.resolve(DEST_NO_ES6), + transform: transformFunc, + target: TYPE_JS, + es5: true, + }); + logger.debug(msg); + const stats = fs.statSync(DEST_NO_ES6); + expect(stats.isFile()).toBe(true); + // eslint-disable-next-line import/no-unresolved, global-require, import/no-dynamic-require + const json = require('./tmp/transformer/test-data-no-es6.js'); + expect(json.foo).toBe(EXPECTED_VALUE); + }); + }); + + describe('Testing Transformer transforming from YAML to JS', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-yaml-js.js'; + + it('should store ' + SRC_YAML + ' file to ' + DEST, async () => { + expect.assertions(2); + const options = { + src: path.resolve(SRC_YAML), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertTransformSuccess(options); + }); + }); + + describe('Testing Transformer transforming from YAML to JSON', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-yaml-json.json'; + + it('should store ' + SRC_YAML + ' file to ' + DEST, async () => { + const options = { + src: path.resolve(SRC_YAML), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertTransformSuccess(options, false); + }); + }); + + describe('Testing Transformer transforming from JSON to JS', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-json-js.js'; + + it('should store ' + SRC_JSON + ' file to ' + DEST, async () => { + const options = { // TODO function for options + src: path.resolve(SRC_JSON), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertTransformSuccess(options); + }); + }); + + describe('Testing Transformer transforming from JS to JSON', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-js-json.json'; + + it('should store ' + SRC_JS + ' file to ' + DEST, async () => { + expect.assertions(2); + const options = { + src: path.resolve(SRC_JS), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertTransformSuccess(options, false); + }); + }); + + describe('Testing Transformer transforming from JS to YAML', () => { + expect.assertions(2); + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-js-yaml.yaml'; + + it('should store ' + SRC_JS + ' file to ' + DEST, async () => { + const options = { + src: path.resolve(SRC_JS), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertYamlTransformSuccess(options); + }); + }); + + describe('Testing Transformer transforming from YAML to YAML', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-yaml-yaml.yaml'; + + it('should store ' + SRC_YAML + ' file to ' + DEST, async () => { + const options = { + src: path.resolve(SRC_YAML), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertYamlTransformSuccess(options); + }); + }); + + describe('Testing Transformer transforming from JSON to JSON', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-json-json.json'; + + it('should store ' + SRC_JSON + ' file to ' + DEST, async () => { + const options = { + src: path.resolve(SRC_JSON), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertTransformSuccess(options, false); + }); + }); + + describe('Testing Transformer transforming from JSON to YAML', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-json-yaml.yaml'; + + it('should store ' + SRC_JSON + ' file to ' + DEST, async () => { + const options = { + src: path.resolve(SRC_JSON), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertYamlTransformSuccess(options); + }); + }); + + describe('Testing Transformer transforming from JS to JS', () => { + const DEST = TRANSFORMER_TEST_BASE_DIR + '/test-data-transform-js-js.js'; + + it('should store ' + SRC_JS + ' file to ' + DEST, async () => { + expect.assertions(2); + const options = { + src: path.resolve(SRC_JS), + transform: transformFunc, + dest: path.resolve(DEST), + }; + await assertTransformSuccess(options); + }); + }); +}); diff --git a/test/functional/test-writer.js b/test/functional/test-writer.js new file mode 100644 index 0000000..c3f8fa8 --- /dev/null +++ b/test/functional/test-writer.js @@ -0,0 +1,453 @@ +import promisify from 'promisify-es6'; +import fsExtra from 'fs-extra'; +import fs from 'fs'; +import os from 'os'; +import stream from 'stream'; +import { write } from '../../src/writer'; +import { logger } from '../logger'; +import { TEST_SUITE_DESCRIPTION_FUNCTIONAL } from '../helper-constants'; +import { + TYPE_YAML, + TYPE_JS, + TYPE_JSON, +} from '../../src/constants'; + +const fsPromised = promisify(fs); + +/** + * @module jy-transform:unit-test:test-writer + * @description This unit test suite checks the validity and correctness of Writer module. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_FUNCTIONAL + ' - writer - ', () => { + /** + * Sample JS content used in tests. + * + * @type {{test: string}} + * @constant + * @private + */ + const JS_CONTENT = { test: 'value' }; + + /** + * Temporary base dir for writer test output. + * @type {string} + * @constant + * @private + */ + const WRITER_TEST_BASE_DIR = './test/functional/tmp/writer'; + + beforeAll(() => { + fsExtra.ensureDirSync(WRITER_TEST_BASE_DIR); + fsExtra.emptyDirSync(WRITER_TEST_BASE_DIR); + }); + + /** + * Asserts that the given `dest` is an existing file. + * + * @param {string} dest - File destination to assert. + * @returns {Error} If `dest` does not exist and `done` is not passed. + * @private + */ + const expectDestFileExists = async (dest) => { + try { + const stats = await fsPromised.stat(dest); + expect(stats.isFile()).toBeTruthy(); + } catch (err) { + if (err.code === 'ENOENT') { + err.message = 'The input file \'' + dest + '\' does not exists or is not accessible, cause: ' + err.message; + } else { + err.message = 'Some error occurred while accessing input file \'' + dest + '\': ' + + err.code + ', ' + err.message; + } + throw err; + } + }; + + /** + * Asserts that the given file `dest` does not exist. + * + * @param {string} dest - File destination to assert. + * @returns {Error} If `dest` does not exist and `done` is not passed. + * @private + */ + const expectDestFileDoesNotExist = async (dest) => { + // check for existing source file + let statErr; + try { + await fsPromised.stat(dest); + statErr = new Error('Error expected when checking file = ' + dest); + } catch (err) { + logger.debug('Error is EXPECTED: ' + err.stack); + expect(err).toBeDefined(); + expect(err.code).toBe('ENOENT'); + } + if (statErr) { + throw statErr; + } + }; + + /** + * Assert an `Error` for a given writer function. + * + * @param {Object} object - The JS object to write. + * @param {Object} options - The options which potentially produce the error. + * @param {string} [match=Error] - The error name to match. + * @private + */ + const expectWriteError = async (object, options, match = 'Error') => { + expect.assertions(1); + try { + await write(object, options); + } catch (err) { + expect(err.name).toMatch(match); + } + }; + + /** + * Assert an `Error` for a given writer function. + * + * @param {Object} object - The JS object to write. + * @param {Object} options - The options which potentially produce the error. + * @param {Error} [match=Error'] - The error type to match. + * @private + */ + const expectWriteErrorByType = (object, options, match = Error) => { + expect.assertions(1); + return expect(write(object, options)).rejects.toBeInstanceOf(match); + }; + + describe('The write function', () => { + it('should write JS to file', async () => { + expect.assertions(2); + const file = WRITER_TEST_BASE_DIR + '/test-data-by-js-to-file.js'; + const msg = await write(JS_CONTENT, { dest: file }); + expect(msg).toBeDefined(); + await expectDestFileExists(file); + }); + + it('should write JS to stream', async () => { + expect.assertions(2); + const file = WRITER_TEST_BASE_DIR + '/test-data-by-js-stream.js'; + const msg = await write(JS_CONTENT, { dest: fs.createWriteStream(file) }); + expect(msg).toBeDefined(); + await expectDestFileExists(file); + }); + + it('should write JS to stream with exports identifier', async () => { + expect.assertions(4); + const file = WRITER_TEST_BASE_DIR + '/test-data-by-js-stream-with-exports-identifier.js'; + const options = { + target: TYPE_JS, + dest: fs.createWriteStream(file), + exports: 'test', + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + await expectDestFileExists(file); + // eslint-disable-next-line import/no-unresolved, global-require + const object = require('./tmp/writer/test-data-by-js-stream-with-exports-identifier.js').test; + expect(object.test).toBeDefined(); + expect(object.test).toBe('value'); + }); + + it('should fail writing JS to file by invalid exports identifier (\'#3/-\')', () => { + const options = { + dest: WRITER_TEST_BASE_DIR + '/test-data-by-js-stream-with-invalid-exports-identifier.js', + exports: '#3/-', + }; + return expectWriteError(JS_CONTENT, options, 'ValidationError'); + }); + + it('should write JS to stream and fail by invalid exports identifier (\'#3/-\')', () => { + const file = WRITER_TEST_BASE_DIR + '/test-data-by-js-stream-with-invalid-exports-identifier.js'; + const options = { + dest: fs.createWriteStream(file), + exports: '#3/-', + }; + return expectWriteError(JS_CONTENT, options, 'ValidationError'); + }); + + it('should write JS to stream and fail by invalid exports identifier (\'if\')', () => { + const file = WRITER_TEST_BASE_DIR + '/test-data-by-js-stream-with-invalid-exports-identifier.js'; + const options = { + dest: fs.createWriteStream(file), + exports: 'if', + }; + return expectWriteError(JS_CONTENT, options, 'ValidationError'); + }); + + it('should write JS to stream and fail by provoked error', () => { + const errorThrowingStream = new stream.Writable(); + // eslint-disable-next-line no-underscore-dangle, func-names + errorThrowingStream._write = function (chunk, encoding, done) { + logger.debug('stream emitting Error now'); + this.emit('error', new Error('Dummy Error')); + done(); + }; + return expectWriteErrorByType(JS_CONTENT, { + target: TYPE_JS, + dest: errorThrowingStream, + }, Error); + }); + + it('should write JS to JS object', async () => { + expect.assertions(4); + const options = { dest: {} }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + expect(options.dest).toBeDefined(); + expect(Object.prototype.hasOwnProperty.call(options.dest, 'test')).toBeTruthy(); + expect(options.dest.test).toBe('value'); + }); + + it('should reject to write JS to JS object with options.exports == \'\'', async () => { + const options = { + dest: {}, + exports: '', + }; + return expectWriteError(JS_CONTENT, options, 'ValidationError'); + }); + + const exports = 'foo'; + it('should write JS to JS object with options.exports == \'' + exports + '\'', async () => { + expect.assertions(5); + const options = { + dest: {}, + exports, + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + expect(options.dest).toBeDefined(); + expect(Object.prototype.hasOwnProperty.call(options.dest, exports)).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(options.dest[exports], 'test')).toBeTruthy(); + expect(options.dest[exports].test).toBe('value'); + }); + + const invalidIdentifier = '#3/-'; + it('should reject write JS with Error on invalid identifier for options.exports: ' + invalidIdentifier, () => { + const options = { + dest: {}, + exports: invalidIdentifier, + }; + return expectWriteError(JS_CONTENT, options, 'ValidationError'); + }); + + it('should reject write JS with Error on missing destination', () => { + return expectWriteError(JS_CONTENT, {}, 'ValidationError'); + }); + + it('should reject write JS to file by invalid file path', (done) => { + const options = { + dest: WRITER_TEST_BASE_DIR + + '/<>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_' + + 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_' + + 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_' + + 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/test-data-by-js-to-file.js' + }; + write(JS_CONTENT, options) + .then((msg) => { + done(new Error('Error expected, but got success message: ' + msg)); + }) + .catch((err) => { + logger.debug('EXPECTED ERROR: ' + (err.stack ? err.stack : err)); + expect(err).toBeDefined(); + // NOTE: here wo do not get an Error type but simply an Object: + // { + // "errno": -63, + // "code": "ENAMETOOLONG", + // "syscall": "mkdir", + // "path": "..." + // } + expect(err).not.toBeInstanceOf(Error); + expect(typeof err === 'object').toBeTruthy(); + expect(err.code).toBe('ENAMETOOLONG'); + done(); + }); + }, 10000); // TODO timeout needed? + }); + + describe('Testing writeJson(...)', () => { + it('should write JSON to file', async () => { + expect.assertions(2); + const options = { + dest: WRITER_TEST_BASE_DIR + '/test-data-by-json-to-file.json' + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + await expectDestFileExists(options.dest); + }); + + it('should write JSON to stream', async () => { + expect.assertions(2); + const file = WRITER_TEST_BASE_DIR + '/test-data-by-json-stream.json'; + const msg = await write(JS_CONTENT, { + target: TYPE_JSON, + dest: fs.createWriteStream(file), + }); + expect(msg).toBeDefined(); + await expectDestFileExists(file); + }); + + it('should write (stringified) JSON to JS object', async () => { + expect.assertions(4); + const options = { + dest: {}, + target: TYPE_JSON + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + expect(options.dest).toBeDefined(); + const result = JSON.parse(options.dest); + expect(Object.prototype.hasOwnProperty.call(result, 'test')).toBeDefined(); + expect(result.test).toBe('value'); + }); + + it('should write JS to JS object', async () => { + expect.assertions(4); + const options = { + dest: {}, + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + expect(options.dest).toBeDefined(); + expect(Object.prototype.hasOwnProperty.call(options.dest, 'test')).toBeDefined(); + expect(options.dest.test).toBe('value'); + }); + }); + + describe('Testing writeYaml(...)', () => { + it('should write YAML to file', async () => { + expect.assertions(2); + const file = WRITER_TEST_BASE_DIR + '/test-data-by-js-to-file.yaml'; + const msg = await write(JS_CONTENT, { + dest: file + }); + expect(msg).toBeDefined(); + await expectDestFileExists(file); + }); + + it('should write YAML to stream', async () => { + expect.assertions(2); + const file = WRITER_TEST_BASE_DIR + '/test-data-by-js-stream.yaml'; + const msg = await write(JS_CONTENT, { + target: TYPE_YAML, + dest: fs.createWriteStream(file), + }); + expect(msg).toBeDefined(); + await expectDestFileExists(file); + }); + + it('should write stringified YAML to JS object', async () => { + expect.assertions(3); + const options = { + target: TYPE_YAML, + dest: { XXXX: 'YYXXXX' }, + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + expect(options.dest).toBeDefined(); + const key = Object.keys(JS_CONTENT)[0]; + expect(options.dest).toBe(key + ': ' + JS_CONTENT[key] + os.EOL); + }); + + it('should reject with Error by invalid src object', () => { + const invalidYamlJson = () => { + }; + return expectWriteError(invalidYamlJson, { + dest: WRITER_TEST_BASE_DIR + '/test-data-by-js-to-file-invalid.yaml' + }, 'YAMLException'); + }); + + it('should reject with Error on missing destination', () => { + return expectWriteError(JS_CONTENT, {}, 'ValidationError'); + }); + }); + + describe('Testing force overwrite file', () => { + it('should reject when options.dest is a directory', () => { + return expectWriteErrorByType(JS_CONTENT, { + dest: './test/data', + target: TYPE_YAML + }, Error); + }); + + it('should write YAML to stream, overwrite on 2nd write, ' + + 'don\'t overwrite on 3rd write and overwrite on 4th write', async () => { + expect.assertions(13); + const dest = WRITER_TEST_BASE_DIR + '/test-data-file-overwriting.yaml'; + let options = { + indent: 4, + dest, + }; + + const asyncFunctions = [ + async () => { + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + await expectDestFileExists(dest); + return 'overwrite test #1 should initially write YAML to file \'' + dest + '\''; + }, + async () => { + options = { + indent: 4, + dest, + force: true + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + await expectDestFileExists(dest); + await expectDestFileDoesNotExist(WRITER_TEST_BASE_DIR + '/test-data-file-overwriting(1).yaml'); + return 'overwrite test #2 should overwrite existing YAML file \'' + dest + '\''; + }, + async () => { + options = { + indent: 4, + dest, + force: false, + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + await expectDestFileExists(WRITER_TEST_BASE_DIR + '/test-data-file-overwriting(1).yaml'); + return 'overwrite test #3 shouldn\'t overwrite existing YAML file \'' + dest + + '\', but write new file \'' + WRITER_TEST_BASE_DIR + '/test-data-file-overwriting(1).yaml\''; + }, + async () => { + options = { + indent: 4, + dest, + force: true + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + await expectDestFileDoesNotExist(WRITER_TEST_BASE_DIR + '/test-data-file-overwriting(2).yaml'); + return 'overwrite test #4 should overwrite existing YAML file \'' + dest + '\''; + }, + async () => { + options = { + indent: 4, + dest, + }; + const msg = await write(JS_CONTENT, options); + expect(msg).toBeDefined(); + await expectDestFileExists(WRITER_TEST_BASE_DIR + '/test-data-file-overwriting(2).yaml'); + return 'overwrite test #5 shouldn\'t overwrite existing YAML file \'' + dest + + '\' and \'' + WRITER_TEST_BASE_DIR + '/test-data-file-overwriting(1).yaml\', but write ' + + 'new file \'' + WRITER_TEST_BASE_DIR + '/test-data-file-overwriting(2).yaml\''; + } + ]; + + await asyncFunctions.reduce((p, fn, idx) => { + return p.then((msg) => { + if (msg) { + logger.debug('testing overwrite #' + (idx + 1) + '/' + asyncFunctions.length + ': ' + msg); + } else { + logger.debug('testing overwrite #' + (idx) + '/' + asyncFunctions.length + ': started!'); + } + return fn().then(result => result).catch(err => err.message); + }); + }, Promise.resolve()); + }, 5000); // we have to set higher timeout here because some travis jobs failed due to 2 sec timeout! + }); +}); diff --git a/test/helper-constants.js b/test/helper-constants.js new file mode 100644 index 0000000..d5c2a0b --- /dev/null +++ b/test/helper-constants.js @@ -0,0 +1,52 @@ +// eslint-disable-next-line +import chalk from 'chalk'; +import Package from '../package.json'; + +/** + * @module jy-transform:unit:helper-constants + * @description The test suite constants definitions. + * @type {Object} + * @private + */ + +/** + * The unit test suite description for the plugin. + * @type {string} + * @constant + * @public + */ +export const TEST_SUITE_DESCRIPTION_UNIT = chalk.inverse( + 'The ' + chalk.bold(Package.name) + chalk.blue(' UNIT ') + 'Test Suite' +); + +/** + * The unit test suite description for the plugin. + * @type {string} + * @constant + * @public + */ +export const TEST_SUITE_DESCRIPTION_FUNCTIONAL = chalk.inverse( + 'The ' + chalk.bold(Package.name) + chalk.blue(' FUNCTIONAL ') + 'Test Suite' +); + +/** + * The basic test data directory path. + * @type {string} + * @private + */ +export const TEST_DATA_DIR = './test/data'; + +/** + * An expected value from source files. + * @type {string} + * @constant + * @private + */ +export const EXPECTED_VALUE = 'bar'; + +export default { + TEST_SUITE_DESCRIPTION_UNIT, + TEST_SUITE_DESCRIPTION_FUNCTIONAL, + TEST_DATA_DIR, + EXPECTED_VALUE, +}; diff --git a/test/logger.js b/test/logger.js index fe24881..87f1563 100644 --- a/test/logger.js +++ b/test/logger.js @@ -1,27 +1,46 @@ -'use strict'; +import winston from 'winston'; -var assert = require('assert'); -var winston = require('winston'); -var fs = require('fs-extra'); /** - * An indent of 8 SPACEs. + * @module jy-transform:unit:logger + * @description The test suite logger. + * @type {Object} + * @private + */ + +// //////////////////////////////////////////////////////////////////////////// +// PRIVATES +// //////////////////////////////////////////////////////////////////////////// + +/** + * An indent of 0 SPACEs. * * @type {string} + * @constant + * @private */ -var INDENT = ' '; -var TEST_TMP_DIR = './test/tmp'; +const INDENT = ''; + +/** + * A temporary test directory. + * + * @type {string} + * @constant + * @private + */ +const TEST_TMP_DIR = './test/tmp'; /** * This function formats the log string by given options to log. * - * @param {{timestamp: function, level: string, [message: string], [meta: object]}} options - The formatter options. - * @returns {string} - The log string. + * @param {Object} options - The formatter options. + * @returns {string} The log string. * @private */ -var formatter = function(options) { - // Return string will be passed to logger. - return options.timestamp() +' '+ options.level.toUpperCase() +' '+ (undefined !== options.message ? options.message : '') + - (options.meta && Object.keys(options.meta).length ? '\n\t'+ JSON.stringify(options.meta) : '' ); +const formatter = (options) => { + // Return string will be passed to logger. + return (options.timestamp() !== '' ? '[' + options.timestamp() + '] ' : '') + '[' + options.level.toUpperCase() + '] ' + + (undefined !== options.message ? options.message : '') + + (options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : ''); }; /** @@ -30,58 +49,61 @@ var formatter = function(options) { * @type {{filename: string, timestamp: winstonFileOptions.timestamp, formatter: formatter, level: string}} * @private */ -var winstonFileOptions = { - filename: TEST_TMP_DIR + '/test.log', - /** - * Formats the timestamp as {@link Date} ISO string prefixed by an indent. - * - * @see #INDENT - * @returns {string} - The {@link Date} ISO string. - */ - timestamp: function() { - return new Date().toISOString(); - }, - json: false, - formatter: formatter, - level: 'debug' +const winstonFileOptions = { + filename: TEST_TMP_DIR + '/test.log', + /** + * Formats the timestamp as {@link Date} ISO string prefixed by an indent. + * + * @see #INDENT + * @returns {string} - The {@link Date} ISO string. + */ + timestamp: () => { + return new Date().toISOString(); + }, + json: false, + formatter, + level: 'info' }; -fs.ensureDirSync(TEST_TMP_DIR); -fs.emptyDirSync(TEST_TMP_DIR); - /** * Options for winston console logging. * * @type {{timestamp: winstonConsoleOptions.timestamp, formatter: formatter, level: string}} * @private */ -var winstonConsoleOptions = { - /** - * Overwrites the timestamp by indent. - * - * @see #INDENT - * @returns {string} - The indent only. - */ - timestamp: function() { - return INDENT; - }, - formatter: formatter, - level: 'info' +const winstonConsoleOptions = { + /** + * Overwrites the timestamp by indent. + * + * @see #INDENT + * @returns {string} - The indent only. + */ + timestamp: () => { + return INDENT; + }, + formatter, + level: 'info' }; +// //////////////////////////////////////////////////////////////////////////// +// PROTECTED EXPORTS +// //////////////////////////////////////////////////////////////////////////// + /** * The winston logger. * - * @public + * @protected */ -var logger = new (winston.Logger)({ - transports: [ - new (winston.transports.File)(winstonFileOptions), - new (winston.transports.Console)(winstonConsoleOptions) - ], - exitOnError: false +export const logger = new (winston.Logger)({ + transports: [ + new (winston.transports.File)(winstonFileOptions), + new (winston.transports.Console)(winstonConsoleOptions) + ], + exitOnError: false }); logger.info('Test-logger initialized, writing to ', winstonFileOptions.filename); -module.exports = logger; +export default { + logger, +}; diff --git a/test/test-log-wrapper.js b/test/test-log-wrapper.js deleted file mode 100644 index 0d91add..0000000 --- a/test/test-log-wrapper.js +++ /dev/null @@ -1,213 +0,0 @@ -'use strict'; - -var LogWrapper = require('../lib/log-wrapper'); -var assert = require('assert'); - -/** - * @classdesc This unit test suite checks the validity and correctness of {@link LogWrapper} class. - */ -describe('Executing \'jy-transform\' project log wrapper test suite.', function () { - - var infoMsg; - var debugMsg; - var traceMsg; - var errorMsg; - var verboseResultArray = []; - var logWrapper; - - var INFO = 'INFO'; - var DEBUG = 'DEBUG'; - var TRACE = 'TRACE'; - var ERROR = 'ERROR'; - - /** - * A mock logger. - * - * @type {{info: mockLogger.info, debug: mockLogger.debug, error: mockLogger.error}} - * @private - */ - var mockLogger = { - info: function (msg) { - infoMsg = msg; - }, - debug: function (msg) { - debugMsg = msg; - }, - trace: function (msg) { - traceMsg = msg; - }, - error: function (msg) { - errorMsg = msg; - } - }; - - var mockLoggerWithoutDebugFunction = { - info: function (msg) { - infoMsg = msg; - }, - trace: function (msg) { - traceMsg = msg; - }, - error: function (msg) { - errorMsg = msg; - } - }; - - var mockLoggerWithoutTraceFunction = { - info: function (msg) { - infoMsg = msg; - }, - debug: function (msg) { - debugMsg = msg; - }, - error: function (msg) { - errorMsg = msg; - } - }; - - var mockLoggerWithVerboseFunction = { - info: function (msg) { - verboseResultArray.push(msg); - } - }; - - describe('Testing LogWrapper with mockLogger', function () { - - /** - * Resets the mock logger message targets. - */ - beforeEach(function () { - infoMsg = undefined; - debugMsg = undefined; - traceMsg = undefined; - errorMsg = undefined; - logWrapper = new LogWrapper(mockLogger); - }); - - var expected = INFO; - it('should log with ' + expected, function (done) { - logWrapper.info(expected); - assert.equal(infoMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - expected = DEBUG; - it('should log with ' + expected, function (done) { - logWrapper.debug(expected); - assert.equal(debugMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - expected = TRACE; - it('should log with ' + expected, function (done) { - logWrapper.trace(expected); - assert.equal(traceMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - expected = ERROR; - it('should log with ' + expected, function (done) { - logWrapper.error(expected); - assert.equal(errorMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - var verboseExpected = { - origin: 'origin', - target: 'target', - src: 'src', - dest: 'dest', - indent: 'indent' - }; - - it('should log options', function (done) { - logWrapper = new LogWrapper(mockLoggerWithVerboseFunction); - logWrapper.verboseOptions(verboseExpected) - .then(function (options) { - assert.equal(options, verboseExpected, 'passed options: ' + JSON.stringify(options, null, 4) + ' should equal logged options: ' + JSON.stringify(verboseExpected, null, 4)); - assert(verboseResultArray.indexOf('origin: ' + verboseExpected.origin) > -1, 'logger verboseResultArray should contain value ' + 'origin: ' + verboseExpected.origin); - assert(verboseResultArray.indexOf('target: ' + verboseExpected.target) > -1, 'logger verboseResultArray should contain value ' + 'target: ' + verboseExpected.target); - assert(verboseResultArray.indexOf('src: ' + verboseExpected.src) > -1, 'logger verboseResultArray should contain value ' + 'src: ' + verboseExpected.src); - assert(verboseResultArray.indexOf('dest: ' + verboseExpected.dest) > -1, 'logger verboseResultArray should contain value ' + 'dest: ' + verboseExpected.dest); - assert(verboseResultArray.indexOf('indent: ' + verboseExpected.indent) > -1, 'logger verboseResultArray should contain value ' + 'indent: ' + verboseExpected.indent); - done(); - }) - .catch(function (err) { - done(err); - }); - }); - - }); - - describe('Testing LogWrapper with mockLoggerWithoutDebugFunction', function () { - - /** - * Resets the mock logger message targets. - */ - beforeEach(function () { - infoMsg = undefined; - debugMsg = undefined; - traceMsg = undefined; - errorMsg = undefined; - logWrapper = new LogWrapper(mockLoggerWithoutDebugFunction); - }); - - var expected = INFO; - it('should log with ' + expected, function (done) { - logWrapper.info(expected); - assert.equal(infoMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - expected = DEBUG; - it('should log with ' + expected, function (done) { - logWrapper.debug(expected); - assert.equal(infoMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - expected = ERROR; - it('should log with ' + expected, function (done) { - logWrapper.error(expected); - assert.equal(errorMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - }); - - describe('Testing LogWrapper with mockLoggerWithoutTraceFunction', function () { - - /** - * Resets the mock logger message targets. - */ - beforeEach(function () { - infoMsg = undefined; - debugMsg = undefined; - errorMsg = undefined; - logWrapper = new LogWrapper(mockLoggerWithoutTraceFunction); - }); - - var expected = INFO; - it('should log with ' + expected, function (done) { - logWrapper.info(expected); - assert.equal(infoMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - expected = TRACE; - it('should log with ' + expected, function (done) { - logWrapper.trace(expected); - assert.equal(debugMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - expected = ERROR; - it('should log with ' + expected, function (done) { - logWrapper.error(expected); - assert.equal(errorMsg, expected, 'logger message should contain value ' + expected); - done(); - }); - - }); - -}); diff --git a/test/test-middleware.js b/test/test-middleware.js deleted file mode 100644 index ee4c67b..0000000 --- a/test/test-middleware.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; - -var Transformer = require('../index'); -var Middleware = require('../index').middleware; -var identityMiddleware = Middleware.identityMiddleware; -var transformer; -var Promise = require('bluebird'); -var logger; -var assert = require('assert'); -var objectPath = require('object-path'); - -/** - * @classdesc This unit test suite checks the validity and correctness of {@link Middleware} class. - */ -describe('Executing \'jy-transform\' project Middleware test suite.', function () { - - /** - * Middleware function for altering JSON. - * - * @param {object} json - The JSON object o alter. - * @private - */ - function middleware(json) { - - function key1(json) { - objectPath.set(json, 'key1', 'value1'); - logger.info('key1 json: ' + JSON.stringify(json)); - return Promise.resolve(json); - } - - function key2(json) { - objectPath.set(json, 'key2', 'value2'); - logger.info('key2 json: ' + JSON.stringify(json)); - return Promise.resolve(json); - } - - function key3(json) { - objectPath.set(json, 'key3', 'value3'); - logger.info('key3 json: ' + JSON.stringify(json)); - return Promise.resolve(json); - } - - return Promise.all([key1(json), key2(json), key3(json)]) - .then(function(result) { - assert.equal(result.length, 3); - logger.info('all the elements were created'); - logger.info('result: ' + JSON.stringify(result[result.length - 1])); - return Promise.resolve(result[result.length - 1]); - }); - } - - - /** - * Helper function to assert the identity Promise. - * - * @param {function} func - The identity Promise function. - * @param {function} done - Test finish callback. - * @private - */ - function assertIdentityPromise(func, done) { - var json = {test: 'value'}; - func(json) - .then(function (jsonResult) { - assert.deepEqual(jsonResult, json, 'JSON passed in should equals JSON put put out from identity Promise'); - done(); - }) - .catch(function(err) { - done(err); - }); - } - - /** - * Init the test logger. - */ - before(function () { - logger = require('./logger.js'); - transformer = new Transformer(logger); - }); - - describe('Testing Transformer middleware', function () { - - it('should alter json', function (done) { - var options = { - src: {}, - dest: {} - }; - transformer.transform(options, middleware) - .then(function (msg){ - logger.info(msg); - logger.info('options.dest: ' + JSON.stringify(options.dest, null, 4)); - assert.equal(options.dest.key1, 'value1', 'options.dest.key1 should have value: value1, but was ' + options.dest.key1); - assert.equal(options.dest.key2, 'value2', 'options.dest.key1 should have value: value2, but was ' + options.dest.key2); - assert.equal(options.dest.key3, 'value3', 'options.dest.key1 should have value: value3, but was ' + options.dest.key3); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - }); - - describe('Testing middleware.identityMiddleware()', function () { - - it('should provide passed function', function (done) { - var func = identityMiddleware; - assert(typeof func === 'function'); - assert(func === identityMiddleware, 'func should be same function identityMiddleware'); - - var json = {}; - identityMiddleware(json) - .then(function (jsonIdentity) { - assert(jsonIdentity === json, 'should return same json'); - done(); - }); - }); - - }); - - describe('Testing middleware.ensureMiddleware()', function () { - - it('should provide passed function', function (done) { - var func = Middleware.ensureMiddleware(identityMiddleware); - assert(typeof func === 'function'); - assert(func === identityMiddleware, 'should return passed function (identityMiddleware)'); - done(); - }); - - it('should reject Promise if middleware passed is not a function type', function (done) { - Middleware.ensureMiddleware({}) - .catch(function (err) { - assert.notEqual(err, null, 'Promise rejection err should not be null'); - assert(err instanceof TypeError); - done(); - }); - }); - - it('should provide identity Promise if middleware passed is null', function (done) { - var func = Middleware.ensureMiddleware(); - assert(typeof func === 'function'); - var json = {test: 'value'}; - assertIdentityPromise(func, done); - }); - - it('should provide identity Promise if middleware passed is undefined', function (done) { - var func = Middleware.ensureMiddleware(undefined); - assert(typeof func === 'function'); - assertIdentityPromise(func, done); - }); - - }); -}); diff --git a/test/test-options-handler.js b/test/test-options-handler.js deleted file mode 100644 index 5aa67bf..0000000 --- a/test/test-options-handler.js +++ /dev/null @@ -1,552 +0,0 @@ -'use strict'; - -var Constants = require('../lib/constants'); -var assert = require('assert'); -//var YAMLException = require('js-yaml/lib/js-yaml/exception'); -var fs = require('fs'); -var path = require('path'); -var OptionsHandler = require('../lib/options-handler'); -var optionsHandler; -var logger; - -/** - * @classdesc This unit test suite checks the validity and correctness of {@link OptionsHandler} class. - */ -describe('Executing \'jy-transform\' project OptionsHandler test suite.', function () { - - /** - * Init the test logger and Writer. - */ - before(function () { - logger = require('./logger.js'); - optionsHandler = new OptionsHandler(logger); - }); - - /** - * Assert an `Error` for a given options function. - * - * @param {object} options - The options which potentially produce the error. - * @param {function} optionsFunc - The function to call for assertion. - * @param {function} done - Test finish callback. - * @param {Error} [errorType=Error] - The error type to assert. - */ - function assertOptionsError(options, optionsFunc, done, errorType) { - optionsFunc(options) - .then(function (resultOptions) { - done(new Error('Error expected when calling options = ' + JSON.stringify(options, null, 4))); - }) - .catch(function (err) { - logger.info('Error is EXPECTED: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - var type = errorType; - if (!type) { - type = Error; - } - logger.debug('ERROR type = ' + (typeof err)); - assert(err instanceof Error); - assert.equal(err.name, type.name, err.name + ' should equal ' + type.name); - done(); - }); - } - - describe('Testing OptionsHandler.validateTransformation(...)', function () { - - it('should reject when options is missing', function (done) { - assertOptionsError(null, optionsHandler.validateTransformation, done); - }); - - it('should reject when options.origin is missing', function (done) { - var options = { - target: Constants.YAML - }; - assertOptionsError(options, optionsHandler.validateTransformation, done); - }); - - it('should reject when options.target is missing', function (done) { - var options = { - origin: Constants.YAML - }; - assertOptionsError(null, optionsHandler.validateTransformation, done); - }); - - it('should resolve transformation correctly from valid origin and target', function (done) { - var options = { - origin: Constants.YAML, - target: Constants.JS - }; - optionsHandler.validateTransformation(options) - .then(function (results) { - assert.equal(results.length, 2, 'result should have length 2'); - assert(results[0] === options); - assert(results[1] === (Constants.YAML + '2' + Constants.JS)); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should reject with Error due to invalid target', function (done) { - var invalidOptions = { - origin: Constants.YAML, - target: 'INVALID_TARGET' - }; - assertOptionsError(invalidOptions, optionsHandler.validateTransformation, done); - }); - - }); - - describe('Testing OptionsHandler.completeOptions(...)', function () { - - it('should reject when options is missing', function (done) { - assertOptionsError(null, optionsHandler.completeOptions, done); - }); - - it('should resolve options.src/origin and options.dest/target with default values (' + Constants.DEFAULT_ORIGIN + '/' + Constants.DEFAULT_TARGET + ')', function (done) { - var PATH_WITH_INVALID_EXT = 'PATH_WITH_INVALID.EXT'; - var options = { - src: PATH_WITH_INVALID_EXT, - dest: PATH_WITH_INVALID_EXT - }; - optionsHandler.completeOptions(options) - .then(function (resultOptions) { - assert.equal(resultOptions.origin, Constants.DEFAULT_ORIGIN, 'options.origin should have value ' + Constants.DEFAULT_ORIGIN); - assert.equal(resultOptions.target, Constants.DEFAULT_TARGET, 'options.target should have value ' + Constants.DEFAULT_TARGET); - assert.equal(resultOptions.dest, PATH_WITH_INVALID_EXT, 'options.dest should have value ' + PATH_WITH_INVALID_EXT); - assert.equal(resultOptions.indent, Constants.DEFAULT_INDENT, 'options.indent should have value ' + Constants.DEFAULT_INDENT); - assert.equal(resultOptions.force, Constants.DEFAULT_FORCE_FILE_OVERWRITE, 'options.indent should have value ' + Constants.DEFAULT_FORCE_FILE_OVERWRITE); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve options.force should result to ' + !Constants.DEFAULT_FORCE_FILE_OVERWRITE, function (done) { - var PATH_WITH_INVALID_EXT = 'PATH_WITH_INVALID.EXT'; - var force = !Constants.DEFAULT_FORCE_FILE_OVERWRITE; - var options = { - src: PATH_WITH_INVALID_EXT, - dest: PATH_WITH_INVALID_EXT, - force: force - }; - optionsHandler.completeOptions(options) - .then(function (resultOptions) { - assert.equal(resultOptions.origin, Constants.DEFAULT_ORIGIN, 'options.origin should have value ' + Constants.DEFAULT_ORIGIN); - assert.equal(resultOptions.target, Constants.DEFAULT_TARGET, 'options.target should have value ' + Constants.DEFAULT_TARGET); - assert.equal(resultOptions.dest, PATH_WITH_INVALID_EXT, 'options.dest should have value ' + PATH_WITH_INVALID_EXT); - assert.equal(resultOptions.indent, Constants.DEFAULT_INDENT, 'options.indent should have value ' + Constants.DEFAULT_INDENT); - assert.equal(resultOptions.force, force, 'options.force should have value ' + force); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - }); - - describe('Testing OptionsHandler.ensureIndent(...)', function () { - - it('should reject when options is missing', function (done) { - assertOptionsError(null, optionsHandler.ensureIndent, done); - }); - - it('should set default indent if indent is missing', function (done) { - var options = {}; - optionsHandler.ensureIndent(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.indent, null, 'options should contain indent but is missing'); - assert.equal(resultOptions.indent, Constants.DEFAULT_INDENT, 'result indent should have length ' + Constants.DEFAULT_INDENT); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should set default indent if indent < minimum indent', function (done) { - var options = { - indent: (Constants.MIN_INDENT - 1), - target: Constants.YAML - }; - optionsHandler.ensureIndent(options) - .then(function (resultOptions) { - assert.equal(resultOptions.target, Constants.YAML, 'result target should have length ' + Constants.YAML); - assert.notEqual(resultOptions.indent, null, 'options should contain indent but is missing'); - assert.equal(resultOptions.indent, Constants.DEFAULT_INDENT, 'result indent should have length ' + Constants.DEFAULT_INDENT); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should set default indent if indent < JS/JSON minimum indent', function (done) { - var options = { - indent: Constants.MIN_INDENT - 1 - }; - optionsHandler.ensureIndent(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.indent, null, 'options should contain indent but is missing'); - assert.equal(resultOptions.indent, Constants.DEFAULT_INDENT, 'result indent should have length ' + Constants.DEFAULT_INDENT); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should set default indent if indent > than maximum indent', function (done) { - var options = { - indent: Constants.MAX_INDENT + 1 - }; - optionsHandler.ensureIndent(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.indent, null, 'options should contain indent but is missing'); - assert.equal(resultOptions.indent, Constants.DEFAULT_INDENT, 'result indent should have length ' + Constants.DEFAULT_INDENT); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - }); - - describe('Testing OptionsHandler.assertOrigin(...)', function () { - - it('should reject when options is missing', function (done) { - assertOptionsError(null, optionsHandler.assertOrigin, done); - }); - - it('should resolve options.origin for valid type YAML', function (done) { - var options = { - origin: Constants.YAML - }; - optionsHandler.assertOrigin(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.origin, null, 'options should contain origin but is missing'); - assert.equal(resultOptions.origin, Constants.YAML, 'result origin should have type ' + Constants.YAML); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve options.origin for valid type JS', function (done) { - var options = { - origin: Constants.JS - }; - optionsHandler.assertOrigin(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.origin, null, 'options should contain origin but is missing'); - assert.equal(resultOptions.origin, Constants.JS, 'result origin should have type ' + Constants.JS); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve options.origin for valid type JSON', function (done) { - var options = { - origin: Constants.JSON - }; - optionsHandler.assertOrigin(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.origin, null, 'options should contain origin but is missing'); - assert.equal(resultOptions.origin, Constants.JSON, 'result origin should have type ' + Constants.JSON); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should reject when options.origin is invalid type', function (done) { - var options = { - origin: 'INVALID_TYPE' - }; - assertOptionsError(options, optionsHandler.assertOrigin, done); - }); - - }); - - describe('Testing OptionsHandler.assertTarget(...)', function () { - - it('should reject when options is missing', function (done) { - assertOptionsError(null, optionsHandler.assertTarget, done); - }); - - it('should resolve options.target for valid type YAML', function (done) { - var options = { - target: Constants.YAML - }; - optionsHandler.assertTarget(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.target, null, 'options should contain target but is missing'); - assert.equal(resultOptions.target, Constants.YAML, 'result target should have type ' + Constants.YAML); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve options.target for valid type JS', function (done) { - var options = { - target: Constants.JS - }; - optionsHandler.assertTarget(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.target, null, 'options should contain target but is missing'); - assert.equal(resultOptions.target, Constants.JS, 'result target should have type ' + Constants.JS); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve options.target for valid type JSON', function (done) { - var options = { - target: Constants.JSON - }; - optionsHandler.assertTarget(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.target, null, 'options should contain target but is missing'); - assert.equal(resultOptions.target, Constants.JSON, 'result target should have type ' + Constants.JSON); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should reject when options.target is invalid type', function (done) { - var options = { - origin: 'INVALID_TYPE' - }; - assertOptionsError(options, optionsHandler.assertTarget, done); - }); - - }); - - describe('Testing OptionsHandler.ensureDest(...)', function () { - - it('should reject when options is missing', function (done) { - assertOptionsError(null, optionsHandler.ensureDest, done); - }); - - it('should resolve options.dest with value \'' + Constants.DEFAULT_OPTIONS.dest + '\' to relative file path to ' + Constants.YAML + ' file', function (done) { - var fileBaseName = 'test'; - var options = { - src: fileBaseName + '.' + Constants.JS, - dest: Constants.DEFAULT_OPTIONS.dest, - target: Constants.YAML - }; - optionsHandler.ensureDest(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.dest, null, 'options should contain dest but is missing'); - assert.equal(resultOptions.dest, fileBaseName + '.' + Constants.YAML, 'result options.dest should have type ' + fileBaseName + '.' + Constants.YAML); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve options.dest with value \'' + Constants.DEFAULT_OPTIONS.dest + '\' to relative file path to ' + Constants.JS + ' file', function (done) { - var fileBaseName = 'test'; - var options = { - src: fileBaseName + '.' + Constants.YAML, - dest: Constants.DEFAULT_OPTIONS.dest, - target: Constants.JS - }; - optionsHandler.ensureDest(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.dest, null, 'options should contain dest but is missing'); - assert.equal(resultOptions.dest, fileBaseName + '.' + Constants.JS, 'result options.dest should have type ' + fileBaseName + '.' + Constants.JS); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve options.dest with value \'' + Constants.DEFAULT_OPTIONS.dest + '\' to relative file path to ' + Constants.JSON + ' file', function (done) { - var fileBaseName = 'test'; - var options = { - src: fileBaseName + '.' + Constants.YAML, - dest: Constants.DEFAULT_OPTIONS.dest, - target: Constants.JSON - }; - optionsHandler.ensureDest(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.dest, null, 'options should contain dest but is missing'); - assert.equal(resultOptions.dest, fileBaseName + '.' + Constants.JSON, 'result options.dest should have type ' + fileBaseName + '.' + Constants.JSON); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should reject options.dest when invalid target type is provided', function (done) { - var fileBaseName = 'test'; - var options = { - src: fileBaseName + '.' + Constants.YAML, - dest: Constants.DEFAULT_OPTIONS.dest, - target: 'INVALID_TARGET' - }; - assertOptionsError(options, optionsHandler.ensureDest, done); - }); - - it('should reject when Writable is given but not target', function (done) { - var options = { - dest: fs.createWriteStream('./test/tmp/myOutput.txt') - }; - assertOptionsError(options, optionsHandler.ensureDest, done); - }); - - it('should resolve original options.dest', function (done) { - var destObj = {}; - var options = { - dest: destObj - }; - optionsHandler.ensureDest(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.dest, null, 'options should contain dest but is missing'); - assert.equal(resultOptions.dest, destObj, 'result options.dest should have type ' + destObj); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve with null value for options.dest', function (done) { - var destObj = {}; - var options = { - }; - optionsHandler.ensureDest(options) - .then(function (resultOptions) { - assert.equal(resultOptions.dest, null, 'options.dest should be null'); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - }); - - describe('Testing OptionsHandler.ensureSrc(...)', function () { - - it('should reject when options is missing', function (done) { - assertOptionsError(null, optionsHandler.ensureSrc, done, TypeError); // TODO heck if this really TypeError!?! - }); - - it('should reject when options.src is not given', function (done) { - var options = {}; - assertOptionsError(options, optionsHandler.ensureSrc, done); - }); - - it('should resolve original options.src', function (done) { - var existingFile = path.resolve('./test/data/test-file.yaml'); - var options = { - src: existingFile - }; - - optionsHandler.ensureSrc(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.src, null, 'options should contain src but is missing'); - assert.equal(resultOptions.src, existingFile, 'result options.src should have file ' + existingFile); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should reject when options.src has value of not existing file', function (done) { - var notExistingFile = 'NON_EXISTING_FILE'; - var options = { - src: notExistingFile - }; - assertOptionsError(options, optionsHandler.ensureSrc, done); - }); - - it('should reject when options.src is a directory', function (done) { - var dir = './test/data'; - var options = { - src: dir - }; - assertOptionsError(options, optionsHandler.ensureSrc, done); - }); - - it('should reject when Readable is given but not origin', function (done) { - var options = { - src: fs.createReadStream('./test/data/readable-test-dummy.txt') - }; - assertOptionsError(options, optionsHandler.ensureSrc, done); - }); - - it('should resolve original options.src Readable', function (done) { - var readable = fs.createReadStream('./test/data/readable-test-dummy.txt'); - var options = { - src: readable, - origin: Constants.JSON - }; - optionsHandler.ensureSrc(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.src, null, 'options should contain src but is missing'); - assert.equal(resultOptions.src, readable, 'result options.src should have type Readable'); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - it('should resolve original options.src object', function (done) { - var srcObj = {}; - var options = { - src: srcObj - }; - optionsHandler.ensureSrc(options) - .then(function (resultOptions) { - assert.notEqual(resultOptions.src, null, 'options should contain src but is missing'); - assert.equal(resultOptions.src, srcObj, 'result options.src should have type ' + srcObj); - done(); - }) - .catch(function (err) { - logger.error('UNEXPECTED ERROR: ' + err.stack); - done(err); - }); - }); - - }); -}); diff --git a/test/test-reader.js b/test/test-reader.js deleted file mode 100644 index da4fe0c..0000000 --- a/test/test-reader.js +++ /dev/null @@ -1,510 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var YAMLException = require('js-yaml/lib/js-yaml/exception'); -var fs = require('fs'); -var Reader = require('../index').Reader; -var Constants = require('../index').constants; -var logger; -var reader; - -/** - * @classdesc This unit test suite checks the validity and correctness of {@link Reader} class. - */ -describe('Executing \'jy-transform\' project Reader test suite.', function () { - - /** - * Init the test logger and Reader. - */ - before(function () { - logger = require('./logger.js'); - reader = new Reader(logger); - }); - - describe('Testing Reader.readJs(...)', function () { - - var exports = 'fooBar'; - var exportsNotExists = 'notFooBar'; - var invalidIdentifier = '#3/-'; - - it('should read JS from file', function (done) { - - var options = { - src: './test/data/test-data.js' - }; - - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null); - assert.equal(json.myproperty, 'old value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read JS from file with options.imports == \'\'', function (done) { - - var options = { - src: './test/data/test-data.js', - imports: '' - }; - - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null); - assert.equal(json.myproperty, 'old value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - - it('should read JS from file with options.imports == \'' + exports + '\'', function (done) { - - var options = { - src: './test/data/test-imports.js', - imports: exports - }; - - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null, 'json should not be null, was: ' + JSON.stringify(json)); - assert(!json.hasOwnProperty(exports), 'json should not have \'' + exports + '\' property, was: ' + JSON.stringify(json)); - assert(!json.hasOwnProperty('bar'), 'json should not have \'bar\' property, was: ' + JSON.stringify(json[exports])); - assert(json.hasOwnProperty('foo'), 'json should have \'foo\' property, was: ' + JSON.stringify(json[exports])); - assert.equal(json.foo, 'bar'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read JS from file with options.imports == \'' + exports + '\' and given origin for unsupported file extension', function (done) { - - var options = { - src: './test/data/test-imports.txt', - imports: exports, - origin: Constants.JS - }; - - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null, 'json should not be null, was: ' + JSON.stringify(json)); - assert(!json.hasOwnProperty(exports), 'json should not have \'' + exports + '\' property, was: ' + JSON.stringify(json)); - assert(!json.hasOwnProperty('bar'), 'json should not have \'bar\' property, was: ' + JSON.stringify(json[exports])); - assert(json.hasOwnProperty('foo'), 'json should have \'foo\' property, was: ' + JSON.stringify(json[exports])); - assert.equal(json.foo, 'bar'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should reject read JS from file with Error on invalid identifier for options.imports: ' + invalidIdentifier, function (done) { - - var options = { - src: './test/data/test-imports.js', - imports: invalidIdentifier - }; - - reader.readJs(options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should reject read JS from file with Error on non-existent identifier for options.imports: ' + exportsNotExists, function (done) { - - var options = { - src: './test/data/test-imports.js', - imports: exportsNotExists - }; - - reader.readJs(options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should read JSON from file', function (done) { - var options = { - src: './test/data/test-data.json' - }; - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null); - assert.equal(json.myproperty, 'old value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read JS from object', function (done) { - var options = { - src: { - test: 'value' - } - }; - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null); - assert.equal(json.test, 'value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read JS from object with options.imports == \'\'', function (done) { - - var options = { - src: { - foo: 'bar' - }, - imports: '' - }; - - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null); - assert.equal(json.foo, 'bar'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read JS from object with options.imports == \'' + exports + '\'', function (done) { - - var options = { - src: { - fooBar: { - bar: 'foo', - foo: 'bar' - } - }, - imports: exports - }; - - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null, 'json should not be null, was: ' + JSON.stringify(json)); - assert(!json.hasOwnProperty(exports), 'json should not have \'' + exports + '\' property, was: ' + JSON.stringify(json)); - assert(json.hasOwnProperty('bar'), 'json should have \'bar\' property, was: ' + JSON.stringify(json[exports])); - assert(json.hasOwnProperty('foo'), 'json should have \'foo\' property, was: ' + JSON.stringify(json[exports])); - assert.equal(json.bar, 'foo'); - assert.equal(json.foo, 'bar'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should reject read JS from object with Error on invalid identifier for options.imports: ' + invalidIdentifier, function (done) { - - var options = { - src: { - fooBar: { - bar: 'foo', - foo: 'bar' - } - }, - imports: invalidIdentifier - }; - - reader.readJs(options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should reject read JS from file with Error on non-existent identifier for options.imports: ' + exportsNotExists, function (done) { - - var options = { - src: { - fooBar: { - bar: 'foo', - foo: 'bar' - } - }, - imports: exportsNotExists - }; - - reader.readJs(options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should read JSON from stream', function (done) { - var options = { - src: fs.createReadStream('./test/data/test-data.json') - }; - reader.readJs(options) - .then(function (json) { - assert.notEqual(json, null); - assert.equal(json.myproperty, 'old value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read corrupted JSON from file path and fail by SyntaxError', function (done) { - var options = { - src: './test/data/test-data-corrupted.json' - }; - reader.readJs(options) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('SyntaxError expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof SyntaxError, 'expected Error should equal SyntaxError, was: ' + (typeof err)); - done(); - }); - }); - - it('should read invalid JSON from file path and fail by SyntaxError', function (done) { - var options = { - src: './test/data/test-data-wrong-syntax.json' - }; - reader.readJs(options) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('SyntaxError expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof SyntaxError, 'expected Error should equal SyntaxError, was: ' + (typeof err)); - done(); - }); - }); - - it('should read corrupted JSON from stream and fail by SyntaxError', function (done) { - var options = { - src: fs.createReadStream('./test/data/test-data-corrupted.json') - }; - reader.readJs(options) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('SyntaxError expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof SyntaxError, 'expected Error message should equal SyntaxError, was: ' + (typeof err)); - done(); - }); - }); - - it('should read invalid JSON from stream and fail by SyntaxError', function (done) { - var options = { - src: fs.createReadStream('./test/data/test-data-wrong-syntax.json') - }; - reader.readJs(options) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('SyntaxError expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof SyntaxError, 'expected Error should equal SyntaxError, was: ' + (typeof err)); - done(); - }); - }); - - it('should fail JS(ON) read by missing options', function (done) { - reader.readJs() - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should fail JS(ON) read by missing options.src', function (done) { - reader.readJs({}) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - }); - - describe('Testing Reader.readYaml(...)', function () { - - it('should read YAML from file', function (done) { - var options = { - src: './test/data/test-data.yaml' - }; - reader.readYaml(options) - .then(function (json) { - assert.notEqual(json, null, 'resulting json should not be null'); - assert.equal(json.myproperty, 'old value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read JS from object', function (done) { - var options = { - src: { - test: 'value' - } - }; - reader.readYaml(options) - .then(function (json) { - assert.notEqual(json, null, 'resulting json should not be null'); - assert.equal(json.test, 'value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read YAML from stream', function (done) { - var options = { - src: fs.createReadStream('./test/data/test-data.yaml') - }; - reader.readYaml(options) - .then(function (json) { - assert.notEqual(json, null, 'resulting json should not be null'); - assert.equal(json.myproperty, 'old value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should read invalid YAML from file path and fail by YAMLException', function (done) { - var options = { - src: './test/data/test-data-wrong-syntax.yaml' - }; - reader.readYaml(options) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('YAMLException expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof YAMLException, 'expected Error should equal YAMLException, was: ' + (typeof err)); - done(); - }); - }); - - it('should read invalid YAML from stream and fail by YAMLException', function (done) { - var options = { - src: fs.createReadStream('./test/data/test-data-wrong-syntax.yaml') - }; - reader.readYaml(options) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('YAMLException expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof YAMLException, 'expected Error message should equal YAMLException, was: ' + (typeof err)); - done(); - }); - }); - - it('should fail YAML read by missing input options', function (done) { - reader.readYaml() - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should fail YAML read by missing options.src', function (done) { - reader.readYaml({}) - .then(function (json) { - assert.equal(json, null, 'json should be null due to expected exception, was: ' + JSON.stringify(json)); - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - }); -}); diff --git a/test/test-transformer.js b/test/test-transformer.js deleted file mode 100644 index 7a8342d..0000000 --- a/test/test-transformer.js +++ /dev/null @@ -1,332 +0,0 @@ -'use strict'; - -var Transformer = require('../index'); -var transformer; -var Constants = require('../lib/constants'); -var logger; -var jsYaml = require('js-yaml'); -var assert = require('assert'); -var Promise = require('bluebird'); -var fs = require('fs-extra'); -var fsPromised = Promise.promisifyAll(require('fs')); -var path = require('path'); - -/** - * @classdesc This unit test suite checks the correct trnasfomration behaviour of {@link Transformer} class. - */ -describe('Executing \'jy-transform\' project\'s Transformer test suite.', function () { - - var TEST_TMP_DIR = './test/tmp'; - var TEST_DATA_DIR = './test/data'; - var SRC = TEST_DATA_DIR + '/test-file.yaml'; - var EXPECTED_VALUE = 5000.00; - - /** - * Init the test logger. - */ - before(function () { - logger = require('./logger.js'); - transformer = new Transformer(logger); - }); - - /** - * Prepare test data. - */ - before(function () { - try { - fs.copySync(SRC, './test/tmp/test-data.yaml'); - logger.info('copied ' + SRC + ' to ' + TEST_TMP_DIR); - } catch (err) { - logger.error('could not copy ' + SRC + ' to ' + TEST_TMP_DIR + err.stack); - throw err; - } - }); - - /** - * Transformation middleware changing value for `total` property. - * - * @param {object} json - To transform. - */ - function middleware(json) { - json.total = EXPECTED_VALUE; - return Promise.resolve(json); - } - - /** - * Helper method which asserts the successful transformation. - * - * @param {object} options - The transformation options. - * @param {function} middleware - The transformation middleware. - * @param {function} done - Test finished callback; - */ - function assertTransformSuccess(options, middleware, done) { - return transformer.transform(options, middleware) - .then(function (msg) { - logger.info(msg); - var stats = fs.statSync(options.dest); - assert(stats.isFile()); - var json = require(path.resolve(options.dest)); - assert.equal(json.total, EXPECTED_VALUE, 'property \'total\' should have new value \'' + EXPECTED_VALUE + '\'.'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - } - - /** - * Helper method which asserts the successful transformation. - * - * @param {object} options - The transformation options. - * @param {function} middleware - The transformation middleware. - * @param {function} done - Test finished callback; - */ - function assertYamlTransformSuccess(options, middleware, done) { - return transformer.transform(options, middleware) - .then(function (msg) { - logger.info(msg); - var stats = fs.statSync(options.dest); - assert(stats.isFile()); - return fs.readFileAsync(options.dest, Constants.UTF8) - .then(function (yaml) { - try { - var json = jsYaml.safeLoad(yaml); - assert.equal(json.total, EXPECTED_VALUE, 'property \'total\' should have new value \'' + EXPECTED_VALUE + '\'.'); - done(); - } catch (err) { // probably a YAMLException - logger.error(err.stack); - return done(err); - } - }); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - } - - - describe('Testing Transformer transforming from YAML to JS to relative path', function () { - - var DEST = TEST_TMP_DIR + '/test-data.js'; - - it('should store ' + DEST + ' file relative to ./test/tmp/test-data.yaml', function (done) { - - var options = { - src: path.resolve('./test/tmp/test-data.yaml') - }; - - transformer.transform(options, middleware) - .then(function (msg) { - logger.info(msg); - var stats = fs.statSync(DEST); - assert(stats.isFile()); - var json = require('./tmp/test-data.js'); - assert.equal(json.total, EXPECTED_VALUE, 'property \'total\' should have new value \'' + EXPECTED_VALUE + '\'.'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - - }); - }); - - describe('Testing Transformer transforming from YAML to JS', function () { - - var SRC = './test/data/test-file.yaml'; - var DEST = TEST_TMP_DIR + '/test-data-transform-yaml-js.js'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - assertTransformSuccess(options, middleware, done); - - }); - }); - - describe('Testing Transformer transforming from YAML to JSON', function () { - - var SRC = './test/data/test-file.yaml'; - var DEST = TEST_TMP_DIR + '/test-data-transform-yaml-json.json'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - assertTransformSuccess(options, middleware, done); - - }); - }); - - describe('Testing Transformer transforming from JSON to JS', function () { - - var SRC = './test/data/test-file.json'; - var DEST = TEST_TMP_DIR + '/test-data-transform-json-js.js'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - assertTransformSuccess(options, middleware, done); - - }); - }); - - describe('Testing Transformer transforming from JS to JSON', function () { - - var SRC = './test/data/test-file.js'; - var DEST = TEST_TMP_DIR + '/test-data-transform-js-json.json'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - assertTransformSuccess(options, middleware, done); - - }); - }); - - describe('Testing Transformer transforming from JS to YAML', function () { - - var SRC = './test/data/test-file.js'; - var DEST = TEST_TMP_DIR + '/test-data-transform-js-yaml.yaml'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - transformer.transform(options, middleware) - .then(function (msg) { - logger.info(msg); - var stats = fs.statSync(options.dest); - assert(stats.isFile()); - - fsPromised.readFileAsync(options.dest, Constants.UTF8) - .then(function (yaml) { - logger.debug('YAML loaded from file ' + options.dest); - try { - var resultJson = jsYaml.safeLoad(yaml); - assert.equal(resultJson.total, EXPECTED_VALUE, 'property \'total\' should have new value \'' + EXPECTED_VALUE + '\'.'); - done(); - } catch (err) { // probably a YAMLException - logger.error('Unexpected error: ' + err.stack); - done(err); - } - }); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - }); - - describe('Testing Transformer transforming from YAML to YAML', function () { - - var SRC = './test/data/test-file.yaml'; - var DEST = TEST_TMP_DIR + '/test-data-transform-yaml-yaml.yaml'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - transformer.transform(options, middleware) - .then(function (msg) { - logger.info(msg); - var stats = fs.statSync(options.dest); - assert(stats.isFile()); - - fsPromised.readFileAsync(options.dest, Constants.UTF8) - .then(function (yaml) { - logger.debug('YAML loaded from file ' + options.dest); - try { - var resultJson = jsYaml.safeLoad(yaml); - assert.equal(resultJson.total, EXPECTED_VALUE, 'property \'total\' should have new value \'' + EXPECTED_VALUE + '\'.'); - done(); - } catch (err) { // probably a YAMLException - logger.error('Unexpected error: ' + err.stack); - done(err); - } - }); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - }); - - describe('Testing Transformer transforming from JSON to JSON', function () { - - var SRC = './test/data/test-file.json'; - var DEST = TEST_TMP_DIR + '/test-data-transform-json-json.json'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - assertTransformSuccess(options, middleware, done); - - }); - }); - - describe('Testing Transformer transforming from JSON to YAML', function () { - - var SRC = './test/data/test-file.json'; - var DEST = TEST_TMP_DIR + '/test-data-transform-json-yaml.yaml'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - assertYamlTransformSuccess(options, middleware, done); - - }); - }); - - describe('Testing Transformer transforming from JS to JS', function () { - - var SRC = './test/data/test-file.js'; - var DEST = TEST_TMP_DIR + '/test-data-transform-js-js.js'; - - it('should store ' + SRC + ' file to ' + DEST, function (done) { - - var options = { - src: path.resolve(SRC), - dest: path.resolve(DEST) - }; - - assertTransformSuccess(options, middleware, done); - - }); - }); - -}); diff --git a/test/test-validator.js b/test/test-validator.js deleted file mode 100644 index d0a0725..0000000 --- a/test/test-validator.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var Promise = require('bluebird'); -var fs = require('fs'); -var os = require('os'); -var stringify = require('json-stringify-safe'); -var stream = require('stream'); -var Validator = require('../lib/validator'); -var logger; -var validator; - -/** - * @classdesc This unit test suite checks validity and correctness. - */ -describe('Executing \'jy-transform\' project Writer test suite.', function () { - - /** - * Init the test logger and Writer. - */ - before(function () { - logger = require('./logger.js'); - validator = new Validator(logger); - }); - - var nonStringIdentifier = {}; - it('should validate non-string identifier to false', function (done) { - assert.equal(validator.validateIdentifier(nonStringIdentifier), false, 'validator should validate non-string identifier \'' + stringify(nonStringIdentifier) + '\' to false'); - done(); - }); - - var invalidIdentifier = '#3/-'; - it('should validate invalid identifier \'' + invalidIdentifier + '\' to false', function (done) { - assert.equal(validator.validateIdentifier(invalidIdentifier), false, 'validator should validate \'' + invalidIdentifier + '\' identifier to false'); - done(); - }); - - var validIdentifier = 'bar'; - it('should validate \'' + validIdentifier + '\' identifier to true', function (done) { - assert.equal(validator.validateIdentifier(validIdentifier), true, 'validator should validate \'' + validIdentifier + '\' identifier to true'); - done(); - }); - -}); diff --git a/test/test-writer.js b/test/test-writer.js deleted file mode 100644 index 2b7a88c..0000000 --- a/test/test-writer.js +++ /dev/null @@ -1,636 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var Promise = require('bluebird'); -var YAMLException = require('js-yaml/lib/js-yaml/exception'); -var fs = require('fs'); -var os = require('os'); -var stream = require('stream'); -var Writer = require('../index').Writer; -var logger; -var writer; - -/** - * @classdesc This unit test suite checks the validity and correctness of {@link Writer} class. - */ -describe('Executing \'jy-transform\' project Writer test suite.', function () { - - /** - * Init the test logger and Writer. - */ - before(function () { - logger = require('./logger.js'); - writer = new Writer(logger); - }); - - /** - * Asserts that the given `dest` is a file. - * - * @param {string} dest - File destination to assert. - * @param {function} [done] - Test's `done` callback. - * @returns {Error} - If dest not exists and `done` is not passed. - * @private - */ - function assertDestFile(dest, done) { - // check for existing source file - try { - var stats = fs.statSync(dest); // TODO could we check this in Async mode? - assert(stats.isFile(), 'write destination ' + dest + ' should be file'); - if (done) { - return done(); - } - } catch (err) { - if (err.code === 'ENOENT') { - err.message = 'The input file \'' + dest + '\' does not exists or is not accessible, cause: ' + err.message; - } else { - err.message = 'Some error occurred while accessing input file \'' + dest + '\': ' + err.code + ', ' + err.message; - } - if (done) { - return done(err); - } - return err; - } - } - - /** - * Asserts that the given `dest` does not exist. - * - * @param {string} dest - File destination to assert. - * @param {function} [done] - Test's `done` callback. - * @returns {Error} - If dest not exists and `done` is not passed. - * @private - */ - function assertNotDestFile(dest, done) { - // check for existing source file - try { - fs.statSync(dest); // TODO could we check this in Async mode? - if (done) { - return done(new Error('Error expected when checking file = ' + dest)); - } - } catch (err) { - logger.info('Error is EXPECTED: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert.equal(err.code, 'ENOENT', 'err.code should equal \'ENOENT\''); - if (done) { - return done(); - } - } - } - - var json = { - test: 'value' - }; - - var errorThrowingStream = new stream.Writable(); - errorThrowingStream._write = function (chunk, encoding, done) { - logger.info('stream emitting Error now'); - this.emit('error', new Error('Dummy Error')); - done(); - }; - - describe('Testing Writer.writeJs(...)', function () { - - it('should write JS to file', function (done) { - - var options = { - dest: './test/tmp/test-data-by-js-to-file.js' - }; - - writer.writeJs(json, options) - .then(function (msg) { - assert.notEqual(msg, null); - assertDestFile(options.dest, done); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write JS to stream', function (done) { - - var file = './test/tmp/test-data-by-js-stream.js'; - var dest = fs.createWriteStream(file); - - var options = { - dest: dest - }; - - writer.writeJs(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile(file, done); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write JS to stream with exports identifier', function (done) { - - var file = './test/tmp/test-data-by-js-stream-with-exports-identifier.js'; - var dest = fs.createWriteStream(file); - - var options = { - dest: dest, - exports: 'test' - }; - - writer.writeJs(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile(file, done); - var json = require('./tmp/test-data-by-js-stream-with-exports-identifier.js').test; - assert.notEqual(json, null, 'json from test identifier should not be null'); - assert.equal(json.test, 'value', 'json from test identifier should have a \'test\' property with value \'value\''); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write JS to file and fail by invalid exports identifier (\'#3/-\')', function (done) { - var dest = './test/tmp/test-data-by-js-stream-with-invalid-exports-identifier.js'; - - var options = { - dest: dest, - exports: '#3/-' - }; - - writer.writeJs(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should write JS to stream and fail by invalid exports identifier (\'#3/-\')', function (done) { - var file = './test/tmp/test-data-by-js-stream-with-invalid-exports-identifier.js'; - var dest = fs.createWriteStream(file); - - var options = { - dest: dest, - exports: '#3/-' - }; - - writer.writeJs(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should write JS to stream and fail by invalid exports identifier (\'if\')', function (done) { - var file = './test/tmp/test-data-by-js-stream-with-invalid-exports-identifier.js'; - var dest = fs.createWriteStream(file); - - var options = { - dest: dest, - exports: 'if' - }; - - writer.writeJs(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - - it('should write JS to stream and fail by provoked error', function (done) { - - var options = { - dest: errorThrowingStream - }; - - writer.writeJs(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should write JS to JS object', function (done) { - - var options = { - dest: {} - }; - - writer.writeJs(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assert.notEqual(options.dest, null, 'options.dest should not be null, was: ' + JSON.stringify(options.dest)); - assert.notEqual(options.dest.hasOwnProperty('test'), null, 'options.dest should have \'test\' property, was: ' + JSON.stringify(options.dest)); - assert.equal(options.dest.test, 'value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write JS to JS object with options.exports == \'\'', function (done) { - - var options = { - dest: {}, - exports: '' - }; - - writer.writeJs(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assert.notEqual(options.dest, null, 'options.dest should not be null, was: ' + JSON.stringify(options.dest)); - assert.notEqual(options.dest.hasOwnProperty('test'), null, 'options.dest should have \'test\' property, was: ' + JSON.stringify(options.dest)); - assert.equal(options.dest.test, 'value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - var exports = 'foo'; - it('should write JS to JS object with options.exports == \'' + exports + '\'', function (done) { - - var options = { - dest: {}, - exports: exports - }; - - writer.writeJs(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assert.notEqual(options.dest, null, 'options.dest should not be null, was: ' + JSON.stringify(options.dest)); - assert(options.dest.hasOwnProperty(exports), 'options.dest should have \'' + exports + '\' property, was: ' + JSON.stringify(options.dest)); - assert(options.dest[exports].hasOwnProperty('test'), 'options.dest.' + exports + ' should have \'test\' property, was: ' + JSON.stringify(options.dest[exports])); - assert.equal(options.dest[exports].test, 'value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - var invalidIdentifier = '#3/-'; - it('should reject write JS with Error on invalid identifier for options.exports: ' + invalidIdentifier, function (done) { - - var options = { - dest: {}, - exports: invalidIdentifier - }; - - writer.writeJs(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should reject write JS with Error on missing destination', function (done) { - - var options = { - }; - - writer.writeJs(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should reject write JS to file by invalid file path', function (done) { - - var options = { - dest: './test/tmp/<>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/test-data-by-js-to-file.js' - }; - - writer.writeJs(json, options) - .then(function (msg) { - assert.notEqual(msg, null); - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - }); - - describe('Testing Writer.writeJson(...)', function () { - - it('should write JSON to file', function (done) { - - var options = { - src: json, - dest: './test/tmp/test-data-by-json-to-file.json' - }; - - writer.writeJson(json, options) - .then(function (msg) { - assert.notEqual(msg, null); - assertDestFile(options.dest, done); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write JSON to stream', function (done) { - - var file = './test/tmp/test-data-by-json-stream.json'; - var dest = fs.createWriteStream(file); - - var options = { - dest: dest - }; - - writer.writeJson(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile(file, done); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write JS to JS object', function (done) { - - var options = { - dest: {} - }; - - writer.writeJson(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assert.notEqual(options.dest, null, 'options.dest should not be null, was: ' + JSON.stringify(options.dest)); - var result = JSON.parse(options.dest); - assert.notEqual(result.hasOwnProperty('test'), null, 'options.dest should have \'test\' property, was: ' + JSON.stringify(options.dest)); - assert.equal(result.test, 'value'); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should reject with Error on missing destination', function (done) { - - var options = { - }; - - writer.writeJson(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - }); - - - describe('Testing Writer.writeYaml(...)', function () { - - it('should write YAML to file', function (done) { - - var options = { - dest: './test/tmp/test-data-by-js-to-file.yaml' - }; - - writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null); - assertDestFile(options.dest, done); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write YAML to stream', function (done) { - - var file = './test/tmp/test-data-by-js-stream.yaml'; - var dest = fs.createWriteStream(file); - - var options = { - dest: dest - }; - - writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile(file, done); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should write stringified YAML to JS object', function (done) { - - var options = { - dest: {} - }; - - writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assert.notEqual(options.dest, null, 'options.dest should not be null, was: ' + JSON.stringify(options.dest)); - assert(typeof options.dest === 'string'); - var key = Object.keys(json)[0]; - assert.equal(options.dest, key + ': ' + json[key] + os.EOL, 'options.dest should contain YAML string, was: ' + JSON.stringify(options.dest)); - done(); - }) - .catch(function (err) { - logger.error(err.stack); - done(err); - }); - }); - - it('should reject with Error by invalid src object', function (done) { - - var options = { - dest: './test/tmp/test-data-by-js-to-file-invalid.yaml' - }; - - var invalidYamlJson = function() {}; - - writer.writeYaml(invalidYamlJson, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof YAMLException, 'expected Error should equal YAMLException, was: ' + (typeof err)); - done(); - }); - }); - - it('should reject with Error on missing destination', function (done) { - - var options = { - }; - - writer.writeYaml(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - }); - - describe('Testing force overwrite file', function () { - - it('should reject when options.dest is a directory', function (done) { - var dir = './test/data'; - var options = { - dest: dir - }; - writer.writeYaml(json, options) - .then(function (msg) { - done(new Error('Error expected')); - }) - .catch(function (err) { - logger.info('EXPECTED ERROR: ' + err.stack); - assert.notEqual(err, null, 'err should not be null'); - assert(err instanceof Error, 'expected Error should equal Error, was: ' + (typeof err)); - done(); - }); - }); - - it('should write YAML to stream, overwrite on 2nd write, don\'t overwrite on 3rd write and overwrite on 4th write', function (done) { - - // we have to set higher timeout here because some travis jobs failed due to 2 sec timeout - this.timeout(10000); - - var dest = './test/tmp/test-data-file-overwriting.yaml'; - - var options = { - indent: 4, - dest: dest - }; - Promise.each([ - function () { - return writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile(dest); - return Promise.resolve('overwrite test #1 should initially write YAML to file \'' + dest + '\''); - }); - }, - function () { - options = { - indent: 4, - dest: dest, - force: true - }; - return writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile(dest); - assertNotDestFile('./test/tmp/test-data-file-overwriting(1).yaml'); - return Promise.resolve('overwrite test #2 should overwrite existing YAML file \'' + dest + '\''); - }); - }, - function () { - return writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile('./test/tmp/test-data-file-overwriting(1).yaml'); - return Promise.resolve('overwrite test #3 shouldn\'t overwrite existing YAML file \'' + dest + '\', but write new file \'./test/tmp/test-data-file-overwriting(1).yaml\''); - }); - }, - function () { - options = { - indent: 4, - dest: dest, - force: false - }; - return writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertNotDestFile('./test/tmp/test-data-file-overwriting(2).yaml'); - return Promise.resolve('overwrite test #4 should overwrite existing YAML file \'' + dest + '\''); - }); - }, - function () { - options = { - indent: 4, - dest: dest - }; - return writer.writeYaml(json, options) - .then(function (msg) { - assert.notEqual(msg, null, 'msg should not be null, was: ' + msg); - assertDestFile('./test/tmp/test-data-file-overwriting(1).yaml'); - return Promise.resolve('overwrite test #5 shouldn\'t overwrite existing YAML file \'' + dest + '\' and \'./test/tmp/test-data-file-overwriting(1).yaml\', but write new file \'./test/tmp/test-data-file-overwriting(2).yaml\''); - }); - } - ], function(value, index, length) { - return value().then(function (msg) { - logger.info('testing overwrite #' + (index + 1) + '/' + length + ': ' + msg); - }); - }).then(function () { - done(); - }).catch(function (err) { - done(err); - }); - }); - - }); - -}); diff --git a/test/unit/test-index.js b/test/unit/test-index.js new file mode 100644 index 0000000..ead9f60 --- /dev/null +++ b/test/unit/test-index.js @@ -0,0 +1,65 @@ +import { transform, read, write, TYPE_YAML, TYPE_JS, TYPE_JSON } from '../../index'; +import { TEST_SUITE_DESCRIPTION_UNIT } from '../helper-constants'; + +// eslint-disable-next-line import/no-commonjs +const index = require('../../index'); + +/** + * @module jy-transform:test-unit:index + * @description This unit test module tests the correct exporting from _./index.js_. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_UNIT + ' - index - ', () => { + describe('Exports Check Unit Tests', () => { + describe('Exports', () => + it('should be an existing Object', () => { + expect.assertions(8); + expect(index).toBeDefined(); + expect(Object.keys(index)).toHaveLength(6); + expect(index.transform).toBeDefined(); + expect(index.transform).toBeInstanceOf(Function); + expect(index.read).toBeDefined(); + expect(index.read).toBeInstanceOf(Function); + expect(index.write).toBeDefined(); + expect(index.write).toBeInstanceOf(Function); + }) + ); + + describe('Exported transform', () => + it('should be an existing function', () => { + expect.assertions(2); + expect(transform).toBeDefined(); + expect(transform).toBeInstanceOf(Function); + }) + ); + + describe('Exported read', () => + it('should be an existing function', () => { + expect.assertions(2); + expect(read).toBeDefined(); + expect(read).toBeInstanceOf(Function); + }) + ); + + describe('Exported write', () => + it('should be an existing function', () => { + expect.assertions(2); + expect(write).toBeDefined(); + expect(write).toBeInstanceOf(Function); + }) + ); + + describe('Exported constants', () => + it('should be existing string values', () => { + expect.assertions(6); + expect(TYPE_YAML).toBeDefined(); + expect(TYPE_YAML).toBe('yaml'); + expect(TYPE_JS).toBeDefined(); + expect(TYPE_JS).toBe('js'); + expect(TYPE_JSON).toBeDefined(); + expect(TYPE_JSON).toBe('json'); + }) + ); + }); +}); diff --git a/test/unit/test-jy-transform.js b/test/unit/test-jy-transform.js new file mode 100644 index 0000000..7e16be8 --- /dev/null +++ b/test/unit/test-jy-transform.js @@ -0,0 +1,48 @@ +import { transform, read, write, TYPE_YAML, TYPE_JS, TYPE_JSON } from '../../src/jy-transform'; +import { TEST_SUITE_DESCRIPTION_UNIT } from '../helper-constants'; + +/** + * @module jy-transform:test-unit:jy-transform + * @description This unit test module tests the correct exporting from _./src/jy-transform.js_. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_UNIT + ' - jy-transform - ', () => { + describe('Exports Check Unit Tests', () => { + describe('Exported transform', () => + it('should be an existing function', () => { + expect.assertions(2); + expect(transform).toBeDefined(); + expect(transform).toBeInstanceOf(Function); + }) + ); + + describe('Exported read', () => + it('should be an existing function', () => { + expect.assertions(2); + expect(read).toBeDefined(); + expect(read).toBeInstanceOf(Function); + }) + ); + + describe('Exported write', () => + it('should be an existing function', () => { + expect.assertions(2); + expect(write).toBeDefined(); + expect(write).toBeInstanceOf(Function); + }) + ); + + describe('Exported constants', () => + it('should be existing string values', () => { + expect.assertions(6); + expect(TYPE_YAML).toBeDefined(); + expect(TYPE_YAML).toBe('yaml'); + expect(TYPE_JS).toBeDefined(); + expect(TYPE_JS).toBe('js'); + expect(TYPE_JSON).toBeDefined(); + expect(TYPE_JSON).toBe('json'); + }) + ); + }); +}); diff --git a/test/unit/test-serialize-utils.js b/test/unit/test-serialize-utils.js new file mode 100644 index 0000000..fe333cc --- /dev/null +++ b/test/unit/test-serialize-utils.js @@ -0,0 +1,101 @@ +import os from 'os'; +import { + createExportString, + serializeJsToString, + serializeJsToJsonString, +} from '../../src/serialize-utils'; +import { TEST_SUITE_DESCRIPTION_UNIT } from '../helper-constants'; + +/** + * @module jy-transform:test-unit:serialize-utils + * @description This unit test suite checks the validity and correctness of JS serialization utility methods. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_UNIT + ' - serialize-utils - ', () => { + const namedExport = 'foo'; + const indent = ' '; + const nl = os.EOL; + const toSerializeToJs = { + foo: 'bar', + bar: { + bar: 'bar' + }, + }; + + describe('Function createExportString', () => { + it('should create ES6 default export', async () => { + expect.assertions(1); + const result = await createExportString(false); + expect(result).toBe('export default '); + }); + + it('should create "module.exports"', async () => { + expect.assertions(1); + const result = await createExportString(true); + expect(result).toBe('module.exports = '); + }); + + it('should create ES6 default export with named export', async () => { + expect.assertions(1); + const result = await createExportString(false, 'foo'); + expect(result).toBe(`export const ${namedExport} = `); + }); + + it('should create "module.exports" with named export', async () => { + expect.assertions(1); + const result = await createExportString(true, 'foo'); + expect(result).toBe(`module.exports.${namedExport} = `); + }); + }); + + describe('Function serializeJsToString', () => { + it('should create "use strict;" if configured', async () => { + expect.assertions(1); + const result = await serializeJsToString(toSerializeToJs, { + strict: true, + indent: indent.length, + es5: false, + }); + expect(result).toBe( + `'use strict;'${nl}${nl}export default {${nl}${indent}foo: 'bar',${nl}${indent}bar: {bar: 'bar'}${nl}};${nl}` + ); + }); + + it('should not create "use strict;" if not configured', async () => { + expect.assertions(1); + const result = await serializeJsToString(toSerializeToJs, { + strict: false, + indent: indent.length, + // es5: false, + }); + expect(result).toBe(`export default {${nl}${indent}foo: 'bar',${nl}${indent}bar: {bar: 'bar'}${nl}};${nl}`); + }); + + it('should serialize all with double quotes if configured', async () => { + expect.assertions(1); + const result = await serializeJsToString(toSerializeToJs, { + strict: true, + indent: indent.length, + es5: true, + double: true, + }); + expect(result).toBe( + `"use strict;"${nl}${nl}module.exports = {${nl}${indent}foo: "bar",${nl}${indent}bar: {bar: "bar"}${nl}};${nl}` + ); + }); + }); + + describe('Function serializeJsToJsonString', () => { + it('should serialize correctly', async () => { + expect.assertions(1); + const toSerializeToJson = { + foo: { + bar: 'bar' + }, + }; + const result = await serializeJsToJsonString(toSerializeToJson, indent.length); + expect(result).toBe(`{${nl}${indent}"foo": {${nl}${indent}${indent}"bar": "bar"${nl}${indent}}${nl}}${nl}`); + }); + }); +}); diff --git a/test/unit/validation/test-joi-extensions-file-utils.js b/test/unit/validation/test-joi-extensions-file-utils.js new file mode 100644 index 0000000..44d8cd9 --- /dev/null +++ b/test/unit/validation/test-joi-extensions-file-utils.js @@ -0,0 +1,28 @@ +import { TEST_SUITE_DESCRIPTION_UNIT } from '../../helper-constants'; +import { isExistingFile } from '../../../src/validation/joi-extensions-file-utils'; + +/** + * @module jy-transform:test-unit:test-joi-extension-file-utils + * @description This unit test module tests validation FS helper method. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_UNIT + ' - joi-extensions-file-utils - ', () => { + describe('Method isExistingFile(pathStr) ', () => { + it('should return true on relative path string with existing file', () => + expect(isExistingFile('test/unit/validation/test-joi-extensions-file-utils.js')).toBe(true) + ); + + it('should return true on relative path string with existing file starting with \'./\'', () => + expect(isExistingFile('./test/unit/validation/test-joi-extensions-file-utils.js')).toBe(true) + ); + + it('should return false on incorrect path string with non-existing file', () => + expect(isExistingFile('/foo/bar/non-exist.html')).toBe(false) + ); + + it('should return false on existing directory path string', () => + expect(isExistingFile('./test')).toBe(false) + ); + }); +}); diff --git a/test/unit/validation/test-joi-extensions-identifier-utils.js b/test/unit/validation/test-joi-extensions-identifier-utils.js new file mode 100644 index 0000000..5ebe5d1 --- /dev/null +++ b/test/unit/validation/test-joi-extensions-identifier-utils.js @@ -0,0 +1,25 @@ +import { isValidEs6Identifier } from '../../../src/validation/joi-extensions-identifier-utils'; +import { TEST_SUITE_DESCRIPTION_UNIT } from '../../helper-constants'; + +/** + * @module jy-transform:unit-test:test-joi-extensions-identifier-utils + * @description This unit test suite checks validity and correctness of ES6 identifiers. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_UNIT + ' - joi-extensions-identifier-utils - ', () => { + const nonStringIdentifier = {}; + it('should validate non-string identifier \'' + JSON.toString(nonStringIdentifier) + '\' to false', () => + expect(isValidEs6Identifier(nonStringIdentifier)).toBe(false) + ); + + const invalidIdentifier = '#3/-'; + it('should validate invalid identifier \'' + invalidIdentifier + '\' to false', () => + expect(isValidEs6Identifier(invalidIdentifier)).toBe(false) + ); + + const validIdentifier = 'bar'; + it('should validate \'' + validIdentifier + '\' identifier to true', () => + expect(isValidEs6Identifier(validIdentifier)).toBe(true) + ); +}); diff --git a/test/unit/validation/test-options-schema-utils.js b/test/unit/validation/test-options-schema-utils.js new file mode 100644 index 0000000..130b28a --- /dev/null +++ b/test/unit/validation/test-options-schema-utils.js @@ -0,0 +1,136 @@ +import stream from 'stream'; +import fs from 'fs'; +import { + inferOriginDefault, + inferTargetDefault, + inferDestDefaultFromSrc, +} from '../../../src/validation/options-schema-utils'; +import { TEST_SUITE_DESCRIPTION_UNIT } from '../../helper-constants'; +import { + TYPE_YAML, + TYPE_JS, + DEFAULT_ORIGIN, + DEFAULT_TARGET, +} from '../../../src/constants'; + +/** + * @module jy-transform:unit-test:test-options-schema-utils + * @description This unit test suite checks the validity and correctness of options schema helper methods. + * @private + */ + +describe(TEST_SUITE_DESCRIPTION_UNIT + ' - options-schema-utils - ', () => { + describe('Function inferOriginDefault', () => { + it('should infer the correct origin from relative path string with existing file having a known file extension', + () => expect(inferOriginDefault({ src: 'test/unit/validation/test-joi-extensions-file-utils.js' })).toBe(TYPE_JS) + ); + + it('should infer the default origin from relative path string with existing file having an unsupported file ' + + 'extension', () => expect(inferOriginDefault({ src: 'test/data/readable-test-dummy.txt' })).toBe(DEFAULT_ORIGIN) + ); + + it('should infer the default origin from relative path string with existing file having no file extension', () => + expect(inferOriginDefault({ src: 'test/data/readable-test-dummy' })).toBe(DEFAULT_ORIGIN) + ); + + it('should infer the correct origin from read stream of existing file having a known file ending', () => + expect(inferOriginDefault({ + src: fs.createReadStream('test/unit/validation/test-joi-extensions-file-utils.js'), + })).toBe(TYPE_JS) + ); + + it('should infer the correct origin from read stream of existing file having an unknown file ending', () => + expect(inferOriginDefault({ src: fs.createReadStream('test/data/readable-test-dummy.txt') })).toBe(DEFAULT_ORIGIN) + ); + + it('should infer the correct origin from plain read stream', () => + expect(inferOriginDefault({ src: new stream.Readable() })).toBe(DEFAULT_ORIGIN) + ); + + it('should infer the default origin from unsupported origin.src', () => + expect(inferOriginDefault({ src: {} })).toBe(DEFAULT_ORIGIN) + ); + }); + + describe('Function inferTargetDefault', () => { + it('should infer the correct target from relative path string with existing file having a known file ending', () => + expect(inferTargetDefault({ dest: 'test/unit/validation/test-joi-extensions-file-utils.yaml' })).toBe(TYPE_YAML) + ); + + it('should infer the default target from relative path string with existing file having an unknown file type', () => + expect(inferTargetDefault({ dest: 'test/data/readable-test-dummy.txt' })).toBe(DEFAULT_TARGET) + ); + + it('should infer the correct target from relative path string with existing file having a known file type', () => + expect(inferTargetDefault({ dest: fs.createWriteStream('test/data/writable-test-dummy.yaml') })).toBe(TYPE_YAML) + ); + + it('should infer the default target from relative path string with existing file having an' + + 'unknown stream file type', () => + expect(inferTargetDefault({ + dest: fs.createWriteStream('test/data/writable-test-dummy.txt'), + })).toBe(DEFAULT_TARGET) + ); + + it('should infer the correct origin from plain write stream', () => + expect(inferTargetDefault({ dest: new stream.Writable() })).toBe(DEFAULT_TARGET) + ); + + it('should infer the default target from unsupported origin.dest', () => + expect(inferTargetDefault({ dest: {} })).toBe(DEFAULT_TARGET) + ); + }); + + describe('Function inferDestDefaultFromSrc', () => { + it('should resolve missing destination from context.src string type without a target to context.src value', () => { + const context = { src: 'test/data/test-data.js' }; + expect(inferDestDefaultFromSrc(context)).toBe(context.src); + }); + + it('should resolve missing destination from context.src file Readable type without a target to' + + 'context.src.path value', () => { + const context = { src: fs.createReadStream('test/data/test-data.js') }; + expect(inferDestDefaultFromSrc(context)).toBe(context.src.path); + }); + + it('should resolve missing destination from context.src file Readable type with a target to' + + 'context.src.path value', () => { + const context = { + src: fs.createReadStream('test/data/test-data.js'), + target: TYPE_JS, + }; + expect(inferDestDefaultFromSrc(context)).toBe(context.src.path); + }); + + it('should resolve missing destination from context.src string type with a target to context.src' + + 'value where extension is replaced with YAML file type', () => { + const context = { + src: 'test/data/test-data.js', + target: TYPE_YAML, + }; + expect(inferDestDefaultFromSrc(context)).toBe('test/data/test-data.yaml'); + }); + + it('should resolve missing destination from context.src string type (without file extension) but with a target to' + + 'context.src value where extension is replaced with YAML file type', () => { + const context = { + src: 'test/data/test-data', + target: TYPE_YAML, + }; + expect(inferDestDefaultFromSrc(context)).toBe('test/data/test-data.yaml'); + }); + + it('should resolve missing destination from context.src file Readable type without a target to' + + 'context.src.path value but with YAML file type', () => { + const context = { + src: fs.createReadStream('test/data/test-data.js'), + target: TYPE_YAML, + }; + expect(inferDestDefaultFromSrc(context)).toBe('test/data/test-data.yaml'); + }); + + it('should resolve missing destination fom context.src Object type to undefined destination value', () => + expect(inferDestDefaultFromSrc({ src: {} })).toBeUndefined() + ); + }); +}); diff --git a/test/unit/validation/test-options-schema.js b/test/unit/validation/test-options-schema.js new file mode 100644 index 0000000..3e7b79b --- /dev/null +++ b/test/unit/validation/test-options-schema.js @@ -0,0 +1,524 @@ +import stringify from 'json-stringify-safe'; +import Stream from 'stream'; +import fs from 'fs'; +import { TEST_SUITE_DESCRIPTION_UNIT } from '../../helper-constants'; +import { + TYPE_YAML, + TYPE_JS, + TYPE_JSON, + DEFAULT_FORCE_FILE_OVERWRITE, + DEFAULT_JS_IMPORTS_IDENTIFIER, + DEFAULT_JS_EXPORTS_IDENTIFIER, + DEFAULT_ORIGIN, + DEFAULT_TARGET, + DEFAULT_INDENT, + MIN_INDENT, + MAX_INDENT, + DEFAULT_STRICT, + DEFAULT_ES5, + DEFAULT_DOUBLE_QUOTES, +} from '../../../src/constants'; +import { + readOptionsSchema, + writeOptionsSchema, +} from '../../../src/validation/options-schema'; +import Joi from '../../../src/validation/joi-extensions'; + +/** + * @module jy-transform:unit-test:test-options-schema + * @description This unit test suite checks the validity and correctness of options schema. + * @private + */ + +/** + * Expect a `ValidationError` for a given options function. + * + * @param {ReadOptions|WriteOptions} invalidOptions - The options which potentially produce the error. + * @param {Schema} schema - The validation schema. + * @private + */ +async function expectOptionsValidationError(invalidOptions, schema) { + expect.assertions(1); + try { + await Joi.validate(invalidOptions, schema); + } catch (err) { + expect(err.name).toMatch('ValidationError'); + } +} + +/** + * Expect a validation success for a given options. + * + * @param {ReadOptions|WriteOptions} validOptions - The options which should be correct. + * @param {Schema} schema - The validation schema. + * @private + */ +function expectOptionsValidationSuccess(validOptions, schema) { + expect.assertions(1); + return expect(Joi.validate(validOptions, schema)).resolves.toMatchObject(validOptions); +} + +describe(TEST_SUITE_DESCRIPTION_UNIT + ' - options-schema - ', () => { + describe('Testing readOptionsSchema validation', () => { + it('should reject when options is missing (null)', () => + expectOptionsValidationError(null, readOptionsSchema) + ); + + it('should reject when options is missing (undefined)', () => + expectOptionsValidationError(undefined, readOptionsSchema) + ); + + it('should set all defaults', async () => { + expect.assertions(2); + const options = { + src: './test/data/test-data.yaml', + dest: './test/tmp/test-data.js', + }; + const validatedOptions = await Joi.validate(options, readOptionsSchema); + expect(validatedOptions.origin).toBe(DEFAULT_ORIGIN); + expect(validatedOptions.imports).toBe(DEFAULT_JS_IMPORTS_IDENTIFIER); + }); + + it('should infer options.origin from file type', async () => { + expect.assertions(1); + const options = { + src: './test/data/test-data.js', // non default type + }; + const validatedOptions = await Joi.validate(options, readOptionsSchema); + expect(validatedOptions.origin).toBe(TYPE_JS); + }); + + it('should infer options.origin from set origin', async () => { + expect.assertions(1); + const options = { + origin: TYPE_JS, + src: new Stream.Readable(), // no inference possible + }; + const validatedOptions = await Joi.validate(options, readOptionsSchema); + expect(validatedOptions.origin).toBe(TYPE_JS); + }); + + it('should infer options.origin to JS from object type', async () => { + expect.assertions(1); + const options = { + src: {}, + dest: 'some-file', + }; + const validatedOptions = await Joi.validate(options, readOptionsSchema); + expect(validatedOptions.origin).toBe(TYPE_JS); + }); + + describe('Testing options.src schema validation', () => { + it('should reject when options.src is an existing directory path string', () => + expectOptionsValidationError({ + src: './test', + dest: 'some-file', + }, readOptionsSchema) + ); + + it('should reject when options.src is undefined', () => + expectOptionsValidationError({ dest: 'some-file' }, readOptionsSchema) + ); + + it('should reject when options.src is null', () => + expectOptionsValidationError({ + src: null, + dest: 'some-file', + }, readOptionsSchema) + ); + + it('should resolve to default origin ' + DEFAULT_ORIGIN + ' when options.src is Stream.Readable and ' + + 'options.origin is not set', async () => { + expect.assertions(2); + const options = { + src: new Stream.Readable(), + dest: new Stream.Writable(), + target: TYPE_YAML, + }; + const validatedOptions = await Joi.validate(options, readOptionsSchema); + expect(validatedOptions.origin).toBe(DEFAULT_ORIGIN); + expect(validatedOptions.target).toBe(TYPE_YAML); + }); + }); + + describe('Testing options.origin schema validation', () => { + it('should resolve with default ' + DEFAULT_ORIGIN + ' when options.origin is undefined and options.src does ' + + 'not allow to infer the type', async () => { + const options = { + src: './test/data/test-data', + dest: 'some-file' + }; + const validatedOptions = await Joi.validate(options, readOptionsSchema); + expect(validatedOptions.origin).toBe(DEFAULT_ORIGIN); + }); + + it('should resolve when options.origin has valid target ' + TYPE_JS, () => { + expectOptionsValidationSuccess({ + src: './test/data/test-data', + dest: 'some-file', + origin: TYPE_JS, + }, readOptionsSchema); + }); + + it('should resolve when options.origin has valid target ' + TYPE_JSON, () => { + expectOptionsValidationSuccess({ + src: './test/data/test-data-json', + dest: 'some-file', + origin: TYPE_JSON, + }, readOptionsSchema); + }); + + it('should resolve when options.origin has valid target ' + TYPE_YAML, () => { + expectOptionsValidationSuccess({ + src: './test/data/test-data-yaml', + dest: 'some-file', + origin: TYPE_YAML, + }, readOptionsSchema); + }); + + it('should reject when options.origin is null', () => + expectOptionsValidationError({ + src: './test/data/test-data-yaml', + dest: 'some-file', + origin: null, + }, readOptionsSchema) + ); + + it('should reject when options.origin is not allowed value', () => + expectOptionsValidationError({ + src: './test/data/test-data-yaml', + dest: 'some-file', + origin: 'not-allowed', + }, readOptionsSchema) + ); + }); + + describe('Testing options.imports schema validation', () => { + const nonStringIdentifier = {}; + it('should reject non-string identifier \'' + stringify(nonStringIdentifier) + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + imports: nonStringIdentifier, + }, readOptionsSchema) + ); + + const invalidIdentifier = '#3/-'; + it('should reject invalid identifier \'' + invalidIdentifier + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + imports: invalidIdentifier, + }, readOptionsSchema) + ); + + const validIdentifier = 'bar'; + it('should accept valid \'' + validIdentifier + '\' identifier', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + imports: validIdentifier, + }, readOptionsSchema) + ); + }); + }); + + describe('Testing writeOptionsSchema validation', () => { + it('should reject when options is missing (null)', () => + expectOptionsValidationError(null, writeOptionsSchema) + ); + + it('should reject when options is missing (undefined)', () => + expectOptionsValidationError(undefined, writeOptionsSchema) + ); + + it('should set all defaults', async () => { + expect.assertions(7); + const options = { + dest: './test/tmp/test-data.js', + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(DEFAULT_TARGET); + expect(validatedOptions.indent).toBe(DEFAULT_INDENT); + expect(validatedOptions.exports).toBe(DEFAULT_JS_EXPORTS_IDENTIFIER); + expect(validatedOptions.force).toBe(DEFAULT_FORCE_FILE_OVERWRITE); + expect(validatedOptions.strict).toBe(DEFAULT_STRICT); + expect(validatedOptions.es5).toBe(DEFAULT_ES5); + expect(validatedOptions.double).toBe(DEFAULT_DOUBLE_QUOTES); + }); + + it('should infer options.target from file type', async () => { + expect.assertions(1); + const options = { + dest: 'some-file.yml', // non default type + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(TYPE_YAML); + }); + + it('should infer options.target from set target', async () => { + expect.assertions(1); + const options = { + target: TYPE_YAML, + dest: new Stream.Writable(), // no inference possible + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(TYPE_YAML); + }); + + it('should infer options.target to JS from object type', async () => { + expect.assertions(1); + const options = { + dest: {}, + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(TYPE_JS); + }); + + describe('Testing options.dest schema validation', () => { + it('should reject when options.dest is undefined', () => + expectOptionsValidationError({ src: './test/data/test-data.js' }, writeOptionsSchema) + ); + + it('should reject when options.dest is null', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: null, + }, writeOptionsSchema) + ); + + it('should resolve output when options.dest is Stream.Writable and options.target is set', async () => { + expect.assertions(1); + const options = { + dest: new Stream.Writable(), + target: TYPE_YAML, + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(TYPE_YAML); + }); + + it('should resolve output when options.dest is Stream.Writable (to YAML file) and options.target is not set', + async () => { + expect.assertions(1); + const options = { + dest: fs.createWriteStream('./test/tmp/test-data.yaml'), + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(TYPE_YAML); + }); + + it('should resolve default ' + DEFAULT_TARGET + ' output when options.dest is Stream.Writable (without path) ' + + 'and options.target is not set)', async () => { + expect.assertions(1); + const options = { + dest: new Stream.Writable(), + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(DEFAULT_TARGET); + }); + }); + + describe('Testing options.target schema validation', () => { + it('should resolve with default ' + DEFAULT_TARGET + ' when options.target is undefined', async () => { + const options = { + dest: 'some-file' + }; + const validatedOptions = await Joi.validate(options, writeOptionsSchema); + expect(validatedOptions.target).toBe(DEFAULT_TARGET); + }); + + it('should resolve when options.target has valid target ' + TYPE_JS, () => { + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + target: TYPE_JS, + }, writeOptionsSchema); + }); + + it('should resolve when options.target has valid target ' + TYPE_JSON, () => { + expectOptionsValidationSuccess({ + src: './test/data/test-data.json', + dest: 'some-file', + target: TYPE_JS, + }, writeOptionsSchema); + }); + + it('should resolve when options.target has valid target ' + TYPE_YAML, () => { + expectOptionsValidationSuccess({ + src: './test/data/test-data.yaml', + dest: 'some-file', + target: TYPE_YAML, + }, writeOptionsSchema); + }); + + it('should reject when options.target is null', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + target: null, + }, writeOptionsSchema) + ); + + it('should reject when options.target is not allowed value', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + target: 'not-allowed', + }, writeOptionsSchema) + ); + }); + + describe('Testing options.exports schema validation', () => { + const nonStringIdentifier = {}; + it('should reject non-string identifier \'' + stringify(nonStringIdentifier) + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + exports: nonStringIdentifier, + }, writeOptionsSchema) + ); + + const invalidIdentifier = '#3/-'; + it('should reject invalid identifier \'' + invalidIdentifier + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + exports: invalidIdentifier, + }, writeOptionsSchema) + ); + + const validIdentifier = 'bar'; + it('should accept valid \'' + validIdentifier + '\' identifier', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + exports: validIdentifier, + }, writeOptionsSchema) + ); + }); + + describe('Testing options.force schema validation', () => { + const notBoolean = {}; + it('should reject non-boolean value \'' + stringify(notBoolean) + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + force: notBoolean, + }, writeOptionsSchema) + ); + + it('should accept valid value \'false\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + force: false, + }, writeOptionsSchema) + ); + + it('should accept valid value \'true\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + force: true, + }, writeOptionsSchema) + ); + }); + + describe('Testing options.indent schema validation', () => { + const notInteger = 0.5; + it('should reject non-integer value \'' + stringify(notInteger) + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + indent: notInteger, + }, writeOptionsSchema) + ); + + it('should accept valid \'' + MIN_INDENT + '\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + indent: MIN_INDENT, + }, writeOptionsSchema) + ); + + it('should accept valid \'' + MAX_INDENT + '\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + indent: MAX_INDENT, + }, writeOptionsSchema) + ); + + it('should reject < \'' + MIN_INDENT + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + indent: MIN_INDENT - 1, + }, writeOptionsSchema) + ); + + it('should reject > \'' + MAX_INDENT + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + indent: MAX_INDENT + 1, + }, writeOptionsSchema) + ); + }); + + describe('Testing options.es5 schema validation', () => { + const notBoolean = {}; + it('should reject non-boolean value \'' + stringify(notBoolean) + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + es5: notBoolean, + }, writeOptionsSchema) + ); + + it('should accept valid value \'false\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + es5: false, + }, writeOptionsSchema) + ); + + it('should accept valid value \'true\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + es5: true, + }, writeOptionsSchema) + ); + }); + + describe('Testing options.double schema validation', () => { + const notBoolean = {}; + it('should reject non-boolean value \'' + stringify(notBoolean) + '\'', () => + expectOptionsValidationError({ + src: './test/data/test-data.js', + dest: 'some-file', + double: notBoolean, + }, writeOptionsSchema) + ); + + it('should accept valid value \'false\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + double: false, + }, writeOptionsSchema) + ); + + it('should accept valid value \'true\'', () => + expectOptionsValidationSuccess({ + src: './test/data/test-data.js', + dest: 'some-file', + double: true, + }, writeOptionsSchema) + ); + }); + }); +});