diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..2bea062 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-es2015-modules-commonjs"] +} diff --git a/.gitignore b/.gitignore index 5148e52..c81664f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ jspm_packages # Optional REPL history .node_repl_history +/dist +.DS_Store diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..4947c89 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +npm-debug.log* +node_modules +jspm_packages +.npm +/.* +/*.* +/test +/lib diff --git a/html.js b/html.js new file mode 100644 index 0000000..34d5f87 --- /dev/null +++ b/html.js @@ -0,0 +1,149 @@ +'use strict'; + +import parse from '@emmetio/abbreviation'; +import snippets from '@emmetio/snippets'; +import createRegistry from '@emmetio/snippets-registry'; +import resolveSnippets from '@emmetio/html-snippets-resolver'; +import Profile from '@emmetio/output-profile'; +import transform from '@emmetio/html-transform'; +import resolveVariables from '@emmetio/variable-resolver'; +import format from '@emmetio/markup-formatters'; + +const defaultOptions = { + /** + * Abbreviation output syntax + * @type {String} + */ + syntax: 'html', + + /** + * Field/tabstop generator for editor. Most editors support TextMate-style + * fields: ${0} or ${1:item}. So for TextMate-style fields this function + * will look like this: + * @example + * (index, placeholder) => `\${${index}{placeholder ? ':' + placeholder : ''}}` + * + * @param {Number} index Placeholder index. Fields with the same indices + * should be linked + * @param {String} [placeholder] Field placeholder + * @return {String} + */ + field: (index, placeholder) => placeholder || '', + + /** + * Predefined snippets registry + * @type {SnippetsRegistry} + */ + registry: null, + + /** + * Insert given text string(s) into expanded abbreviation + * If array of strings is given, the implicitly repeated element (e.g. `li*`) + * will be repeated by the amount of items in array + * @type {String|String[]} + */ + insertText: null, + + /** + * Either predefined output profile or options for output profile. Used for + * abbreviation output + * @type {Profile|Object} + */ + profile: null, + + /** + * Custom variables for variable resolver + * @see @emmetio/variable-resolver + * @type {Object} + */ + variables: {}, + + /** + * Custom predefined snippets for abbreviation. The expanded abbreviation + * will try to match given snippets that may contain custom elements, + * predefined attributes etc. + * May also contain array of items: either snippets (Object) or references + * to default syntax snippets (String; the key in default snippets hash) + * @see @emmetio/snippets + * @type {Object} + */ + snippets: {}, + + /** + * Hash of additional transformations that should be applied to expanded + * abbreviation, like BEM or JSX. Since these transformations introduce + * side-effect, they are disabled by default and should be enabled by + * providing a transform name as a key and transform options as value: + * @example + * { + * bem: {element: '--'}, + * jsx: true // no options, just enable transform + * } + * @see @emmetio/html-transform/lib/addons + * @type {Object} + */ + addons: null, + + /** + * Additional options for syntax formatter + * @see @emmetio/markup-formatters + * @type {Object} + */ + format: null +}; + +const defaultVariables = { + lang: 'en', + locale: 'en-US', + charset: 'UTF-8' +}; + +export default function expand(abbr, options) { + options = Object.assign({}, defaultOptions, options); + options.variables = Object.assign({}, defaultVariables, options.variables); + + const registry = createSnippetsRegistry(options); + + const tree = parse(abbr) + .use(transform, options.insertText, options.addons) + .use(resolveSnippets, registry) + .use(resolveVariables, options.variables); + + return format(tree, createProfile(options), options.syntax, options.format); +} + +/** + * Creates snippets registry and fills it with data + * @param {Object} options + * @return {SnippetsRegistry} + */ +function createSnippetsRegistry(options) { + if (options.registry) { + return options.registry; + } + + const registrySnippets = [snippets[options.syntax]]; + + if (Array.isArray(options.snippets)) { + options.snippets.forEach(item => { + // if array item is a string, treat it as a reference to globally + // defined snippets + registrySnippets.push(typeof item === 'string' ? snippets[item] : item) + }); + } else if (typeof options.snippets === 'object') { + registrySnippets.push(options.snippets); + } + + return createRegistry(registrySnippets.filter(Boolean)); +} + +/** + * Creates output profile from given options + * @param {Object} options + * @return {Profile} + */ +function createProfile(options) { + return options.profile instanceof Profile + ? options.profile + : new Profile(options.profile); +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..725a18f --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +'use strict'; + +import html from './html'; + +// XXX will add CSS support later + +export default function(abbr, options) { + return html(abbr, options); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..79c9da5 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "@emmetio/expand-abbreviation", + "version": "0.0.1", + "description": "Reference implementation of Emmet abbreviation expander", + "main": "dist/expand.cjs.js", + "jsnext:main": "dist/expand.es.js", + "dependencies": { + "@emmetio/abbreviation": "^0.4.6", + "@emmetio/html-snippets-resolver": "^0.1.0", + "@emmetio/html-transform": "^0.2.0", + "@emmetio/markup-formatters": "^0.2.1", + "@emmetio/output-profile": "^0.1.4", + "@emmetio/snippets": "^0.1.0", + "@emmetio/snippets-registry": "^0.1.1", + "@emmetio/variable-resolver": "^0.1.1" + }, + "devDependencies": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", + "babel-register": "^6.18.0", + "mocha": "^3.2.0", + "rollup": "^0.41.1", + "rollup-plugin-node-resolve": "^2.0.0" + }, + "scripts": { + "test": "mocha", + "build": "npm run build:cjs && npm run build:es && npm run build:es:full", + "build:cjs": "rollup -c -o dist/expand.cjs.js -f cjs ./index.js", + "build:es": "rollup -c -o dist/expand.es.js -f es ./index.js", + "build:es:full": "rollup -c ./rollup-full.config.js -o dist/expand-full.es.js ./index.js", + "prepublish": "npm run test && npm run build" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/emmetio/expand-abbreviation.git" + }, + "keywords": [ + "emmet", + "abbreviation", + "expand" + ], + "author": "Sergey Chikuyonok ", + "license": "MIT", + "bugs": { + "url": "https://github.com/emmetio/expand-abbreviation/issues" + }, + "homepage": "https://github.com/emmetio/expand-abbreviation#readme" +} diff --git a/rollup-full.config.js b/rollup-full.config.js new file mode 100644 index 0000000..b32ebb6 --- /dev/null +++ b/rollup-full.config.js @@ -0,0 +1,9 @@ +import nodeResolve from 'rollup-plugin-node-resolve'; + +export default { + entry: './index.js', + format: 'es', + plugins: [nodeResolve({ + jsnext: true + })] +}; diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..2617c2f --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,14 @@ +export default { + entry: './index.js', + format: 'es', + external: [ + '@emmetio/abbreviation', + '@emmetio/snippets', + '@emmetio/snippets-registry', + '@emmetio/html-snippets-resolver', + '@emmetio/output-profile', + '@emmetio/html-transform', + '@emmetio/variable-resolver', + '@emmetio/markup-formatters' + ] +}; diff --git a/test/expand.js b/test/expand.js new file mode 100644 index 0000000..8c158b9 --- /dev/null +++ b/test/expand.js @@ -0,0 +1,11 @@ +'use strict'; + +const assert = require('assert'); +require('babel-register'); +const expand = require('../html').default; + +describe('HTML expand', () => { + it('basic', () => { + console.log(expand('ul>.item$*2')); + }); +});