diff --git a/.editorconfig b/.editorconfig index c2c63f78..bf1fa99d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,3 @@ -# http://editorconfig.org root = true [*] @@ -11,8 +10,3 @@ insert_final_newline = true [*.md] trim_trailing_whitespace = false -insert_final_newline = false - -[test/**/*] -trim_trailing_whitespace = false -insert_final_newline = false diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..9f14403c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,213 @@ +'use strict'; + +module.exports = { + root: true, + extends: 'eslint:recommended', + + env: { + commonjs: true, + es2023: true, + mocha: true, + node: true + }, + + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'script', + requireConfigFile: false + }, + + rules: { + 'accessor-pairs': 2, + 'array-bracket-newline': [1, 'consistent'], + 'array-bracket-spacing': [1, 'never'], + 'array-callback-return': 1, + 'array-element-newline': [1, 'consistent'], + 'arrow-body-style': 0, + 'arrow-parens': [1, 'as-needed'], + 'arrow-spacing': [1, { before: true, after: true }], + 'block-scoped-var': 1, + 'block-spacing': [1, 'always'], + 'brace-style': [1, '1tbs', { allowSingleLine: true }], + 'callback-return': 0, + 'camelcase': [0, { allow: [] }], + 'capitalized-comments': 0, + 'class-methods-use-this': 0, + 'comma-dangle': [1, 'never'], + 'comma-spacing': [1, { before: false, after: true }], + 'comma-style': [1, 'last'], + 'complexity': 1, + 'computed-property-spacing': 1, + 'consistent-return': 0, + 'consistent-this': 1, + 'constructor-super': 2, + 'curly': [1, 'multi-line', 'consistent'], + 'default-case': 1, + 'dot-location': [1, 'property'], + 'dot-notation': 1, + 'eol-last': 1, + 'eqeqeq': [1, 'allow-null'], + 'for-direction': 1, + 'func-call-spacing': 2, + 'generator-star-spacing': [1, { before: true, after: true }], + 'handle-callback-err': [2, '^(err|error)$'], + 'indent': [1, 2, { SwitchCase: 1 }], + 'key-spacing': [1, { beforeColon: false, afterColon: true }], + 'keyword-spacing': [1, { before: true, after: true }], + 'linebreak-style': [1, 'unix'], + 'new-cap': [1, { newIsCap: true, capIsNew: false }], + 'new-parens': 2, + 'no-alert': 1, + 'no-array-constructor': 1, + 'no-async-promise-executor': 1, + 'no-caller': 2, + 'no-case-declarations': 1, + 'no-class-assign': 2, + 'no-cond-assign': 2, + 'no-console': 0, + 'no-const-assign': 2, + 'no-constant-condition': [1, { checkLoops: false }], + 'no-control-regex': 2, + 'no-debugger': 2, + 'no-delete-var': 2, + 'no-dupe-args': 2, + 'no-dupe-class-members': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-duplicate-imports': 1, + 'no-else-return': 0, + 'no-empty-character-class': 2, + 'no-empty-function': 0, + 'no-empty-pattern': 0, + 'no-empty': [1, { allowEmptyCatch: true }], + 'no-eval': 0, + 'no-ex-assign': 2, + 'no-extend-native': 2, + 'no-extra-bind': 1, + 'no-extra-boolean-cast': 1, + 'no-extra-label': 1, + 'no-extra-parens': [1, 'all', { conditionalAssign: false, returnAssign: false, nestedBinaryExpressions: false, ignoreJSX: 'multi-line', enforceForArrowConditionals: false }], + 'no-extra-semi': 1, + 'no-fallthrough': 2, + 'no-floating-decimal': 2, + 'no-func-assign': 2, + 'no-global-assign': 2, + 'no-implicit-coercion': 2, + 'no-implicit-globals': 1, + 'no-implied-eval': 2, + 'no-inner-declarations': [1, 'functions'], + 'no-invalid-regexp': 2, + 'no-invalid-this': 1, + 'no-irregular-whitespace': 2, + 'no-iterator': 2, + 'no-label-var': 2, + 'no-labels': 2, + 'no-lone-blocks': 2, + 'no-lonely-if': 2, + 'no-loop-func': 1, + 'no-mixed-requires': 1, + 'no-mixed-spaces-and-tabs': 2, + 'no-multi-assign': 1, + 'no-multi-spaces': 1, + 'no-multi-str': 2, + 'no-multiple-empty-lines': [1, { max: 1 }], + 'no-native-reassign': 2, + 'no-negated-condition': 0, + 'no-negated-in-lhs': 2, + 'no-new-func': 2, + 'no-new-object': 2, + 'no-new-require': 2, + 'no-new-symbol': 1, + 'no-new-wrappers': 2, + 'no-new': 1, + 'no-obj-calls': 2, + 'no-octal-escape': 2, + 'no-octal': 2, + 'no-path-concat': 1, + 'no-proto': 2, + 'no-prototype-builtins': 0, + 'no-redeclare': 2, + 'no-regex-spaces': 2, + 'no-restricted-globals': 2, + 'no-return-assign': 1, + 'no-return-await': 2, + 'no-script-url': 1, + 'no-self-assign': 1, + 'no-self-compare': 1, + 'no-sequences': 2, + 'no-shadow-restricted-names': 2, + 'no-shadow': 0, + 'no-spaced-func': 2, + 'no-sparse-arrays': 2, + 'no-template-curly-in-string': 0, + 'no-this-before-super': 2, + 'no-throw-literal': 2, + 'no-trailing-spaces': 1, + 'no-undef-init': 2, + 'no-undef': 2, + 'no-unexpected-multiline': 2, + 'no-unneeded-ternary': [1, { defaultAssignment: false }], + 'no-unreachable-loop': 1, + 'no-unreachable': 2, + 'no-unsafe-assignment': 0, + 'no-unsafe-call': 0, + 'no-unsafe-finally': 2, + 'no-unsafe-member-access': 0, + 'no-unsafe-negation': 2, + 'no-unsafe-optional-chaining': 0, + 'no-unsafe-return': 0, + 'no-unused-expressions': 2, + 'no-unused-vars': [1, { vars: 'all', args: 'after-used' }], + 'no-use-before-define': 0, + 'no-useless-call': 2, + 'no-useless-catch': 0, + 'no-useless-escape': 0, + 'no-useless-rename': 1, + 'no-useless-return': 1, + 'no-var': 1, + 'no-void': 1, + 'no-warning-comments': 0, + 'no-with': 2, + 'object-curly-spacing': [2, 'always', { objectsInObjects: true }], + 'object-shorthand': 1, + 'one-var': [1, { initialized: 'never' }], + 'operator-linebreak': [0, 'after', { overrides: { '?': 'before', ':': 'before' } }], + 'padded-blocks': [1, { switches: 'never' }], + 'prefer-const': [1, { destructuring: 'all', ignoreReadBeforeAssign: false }], + 'prefer-promise-reject-errors': 1, + 'quotes': [1, 'single'], + 'radix': 2, + 'rest-spread-spacing': 1, + 'semi-spacing': [1, { before: false, after: true }], + 'semi-style': 1, + 'semi': [1, 'always'], + 'space-before-blocks': [1, 'always'], + 'space-before-function-paren': [1, { anonymous: 'never', named: 'never', asyncArrow: 'always' }], + 'space-in-parens': [1, 'never'], + 'space-infix-ops': 1, + 'space-unary-ops': [1, { words: true, nonwords: false }], + 'spaced-comment': [0, 'always', { markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] }], + 'strict': 2, + 'switch-colon-spacing': 1, + 'symbol-description': 1, + 'template-curly-spacing': [2, 'never'], + 'template-tag-spacing': [2, 'never'], + 'unicode-bom': 1, + 'use-isnan': 2, + 'valid-jsdoc': 1, + 'valid-typeof': 2, + 'wrap-iife': [1, 'any'], + 'yoda': [1, 'never'] + }, + + ignorePatterns: [ + 'node_modules', + 'dist', + 'tmp', + 'temp', + 'lib/templates', + 'support', + 'test/fixtures', + 'test/support' + ] +}; diff --git a/.gitattributes b/.gitattributes index f9f012bc..660957e7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,10 @@ -* text=lf +# Enforce Unix newlines +* text eol=lf + +# binaries +*.ai binary +*.psd binary *.jpg binary *.gif binary *.png binary -*.jpeg binary \ No newline at end of file +*.jpeg binary diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 00000000..80130600 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,43 @@ +# Contributing to Verb + +First and foremost, thank you! We appreciate that you want to contribute to Verb, your time is valuable, and your contributions mean a lot to us. + +**What does "contributing" mean?** + +Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following: + +- Updating or correcting documentation +- Feature requests +- Bug reports + +## Issues + +**Before creating an issue** + +Please make sure you're creating one in the right place: + +- do you have a template syntax question? Like how to accomplish something with handlebars? The best place to get answers for this is [stackoverflow.com][], the [handlebars docs](handlebarsjs.com), or the documentation for the template engine you're using. +- Are you having an issue with an Verb feature that is powered by an underlying lib? This is sometimes difficult to know, but sometimes it can be pretty easy to find out. For example, if you use a glob pattern somewhere and you found what you believe to be a matching bug, that would probably be an issue for [node-glob][] or [micromatch][] + +**Creating an issue** + +Please be as descriptive as possible when creating an issue. Give us the information we need to successfully answer your question or address your issue by answering the following in your issue: + +- what version of assemble are you using? +- is the issue helper-related? If so, this issue should probably be opened on the repo related to the helper being used. +- do you have any custom helpers defined? Is the issue related to the helper itself, data (context) being passed to the helper, or actually registering the helper in the first place? +- are you using middleware? +- any plugins? + + +## Above and beyond + +Here are some tips for creating idiomatic issues. Taking just a little bit extra time will make your issue easier to read, easier to resolve, more likely to be found by others who have the same or similar issue in the future. + +- take some time to learn basic markdown. This [markdown cheatsheet](https://gist.github.com/jonschlinkert/5854601) is super helpful, as is the GitHub guide to [basic markdown](https://help.github.com/articles/markdown-basics/). +- Learn about [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/). And if you want to really go above and beyond, read [mastering markdown](https://guides.github.com/features/mastering-markdown/). +- use backticks to wrap code. This ensures that code will retain its format, making it much more readable to others +- use syntax highlighting by adding the correct language name after the first "code fence" + +[node-glob]: https://github.com/isaacs/node-glob +[micromatch]: https://github.com/jonschlinkert/micromatch \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2ae54b69..9d0dd22e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,25 @@ -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz -*.rar +# always ignore files *.sublime-* -npm-debug.log -.DS* -TODO.md +*.code-* +*.log +.DS_Store +.env +.env.* + +# always ignore dirs +temp +tmp +vendor -# Always-ignore dirs -node_modules/ -tmp/ -temp/ -vendor/ +# test related, or directories generated by tests +.nyc* coverage -support + +# package managers +node_modules +package-lock.json +yarn.lock + +# misc +_draft +TODO.md diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index ec81ac56..00000000 --- a/.jshintrc +++ /dev/null @@ -1,29 +0,0 @@ -{ - "asi": false, - "boss": true, - "camelcase": true, - "curly": false, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "freeze": true, - "funcscope": true, - "immed": true, - "indent": 2, - "latedef": false, - "laxbreak": true, - "laxcomma": false, - "maxcomplexity": 7, - "maxdepth": 3, - "mocha": true, - "newcap": true, - "noarg": true, - "node": true, - "noempty": true, - "nonbsp": true, - "quotmark": "single", - "strict": true, - "sub": true, - "undef": true, - "unused": true -} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0fc9381e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -sudo: false -language: node_js -node_js: - - "0.10" - - "0.12" - - "0.13" - - "iojs" -matrix: - fast_finish: true - allow_failures: - - node_js: "0.13" diff --git a/.verb.md b/.verb.md index a43b07ca..51e73de5 100644 --- a/.verb.md +++ b/.verb.md @@ -1,202 +1,335 @@ -# {%= name %} {%= badge("fury") %} {%= badge("travis") %} +A project without documentation is like a project that doesn't exist. Verb solves this by making it dead simple to generate documentation, using simple markdown templates, with zero configuration required. -> {%= description %} +## What is verb? -**Built by verb** +**Documentation system** -The follow projects use verb to build the reamde and other docs: +Verb is a documentation system that is simple and fast enough to use for [generating a readme][verb-generate-readme] for a GitHub project, yet powerful and smart enough to build the most complex documentation projects around. -- [micromatch](https://github.com/jonschlinkert/micromatch/) (1.7m downloads/mo) - this readme is pretty extensive, with a TOC and other advanced features -- [is-glob](https://www.npmjs.com/package/is-glob) (1.6m downloads/mo) - example of simple readme -- [repeat-string](https://www.npmjs.com/package/is-glob) (2.2m downloads/mo) - example of another basic readme. +**I just want to generate a readme, can verb do this?** -**Quickstart** +Yes! See [verb-generate-readme][]. -Install `verb` and `verb-cli` globally: +**Highly pluggable** + +Verb is also highly pluggable, with first-class plugin support. Moreover, verb also supports [gulp][], [base][], [assemble][], [generate][] and [update][] plugins. + +### Why use verb? + +**Why should I use verb, instead of X?** + +Verb is not a _documentation generator_, it's a documentation system that can be used to create documentation generators like [documentation.js](https://github.com/documentationjs/documentation) and [slate](https://github.com/lord/slate). + +For example, [verb-generate-readme][] is a sophisticated readme generator that renders templates using data from the user's environment, like package.json and git config. For API documentation, verb-readme-generator uses template helpers. + +## Highlights + +**Templating** + +* Render templates with any node.js engine, including [handlebars][], [lodash][], [nunjucks][], and all [consolidate][] engines. +* Convert markdown to HTML, or render markdown templates back to markdown (see [.verb.md](#verbmd)) +* Template collections +* Support for "pages", "partials", and "layouts" +* Helper support (sync and async!) +* Middleware that can be used at any point in the render cycle + +**Helpers** + +* Generate a list of [related links][helpers]{helper-related} to other npm packages +* Resolve [reflinks][helpers]{helper-reflinks} +* More than 150 other helpers available from [template-helpers][] and the [helpers org][github]{helpers} + +**Plugins** + +Verb has rich plugin support, along with native support for [gulp plugins][], so if you want to publish a documentation site, you can do things like: + +* Ignore files marked as "drafts" +* CSS minification or reduction +* SASS or LESS compiling and minification +* JavaScript minification, reduction or concatenation +* Asset copying or renaming +* Image compression +* HTML minification and linting +* RSS feeds +* Anything else you can do with [gulp plugins][] + +**Generators** + +Verb supports [generate][] generators, which are plugins that are registered by name and can be run by command line or API. Generators can also be published to npm and installed globally or locally. + +See the [generate][] project and [documentation][generate]{docs} for more details. + + +## Table of Contents + + + +## Getting started + +### Installing verb + +To use verb's CLI, you will first need to install it globally. You can do that now with the following command: ```sh -$ npm i verb verb-cli -g +$ npm --global install verb ``` -Next, just add a `.verb.md` markdown template to your project and run `verb` in the commandline _(**NOTE** that verb will overwrite the existing `README`, so make sure your work is committed!)_. +This adds the `verb` command to your system path, allowing it to be run from any directory. -I'm working on a site for verb, but in the meantime a good place to see `.verb.md` examples is to surf [my projects](https://github.com/jonschlinkert). +### init -## Install -{%= include("install-npm") %} +If you want to test that verb works, while also initializing settings for the current project, run: -## Usage +```sh +$ verb init +``` -```js -var verb = require('{%= name %}'); +### Generators + +All of the "real work" in verb is accomplished using generators. Now that `verb` is installed, you can run any [generator found on npm](https://www.npmjs.com/search?q=generategenerator) + + +**Install readme generator** + +The `verb-generate-readme` is a good example of what's possible with verb, and it's an easy way to get started. You can install the library with the following command: + +```sh +$ npm i -g verb-generate-readme ``` -## Table of contents - +**.verb.md** -## Features -{%= docs("sections/features.md") %} +You should now be able to generate your project's readme from a `.verb.md` template with the following command: -## CLI +```sh +$ verb readme +``` -_(WIP)_ +Or you can go a step further by adding a `verbfile.js` to your project, allowing you to customize verb with generators, plugins, middleware, helpers, templates and more! -## API +**verbfile.js** -> Verb's API is organized into the following categories: +Next, create a `verbfile.js` with the following code: -* [Template API](#template-api) -* [Config API](#config-api) -* [Data API](#data-api) -* [Middleware API](#middleware-api) -* [Task API](#task-api) +```js +module.exports = function(app) { + app.extendWith('verb-generate-readme'); + // call the `readme` task from verb-generate-readme + app.task('default', ['readme']); +}; +``` -### Template API +## verbfile.js -_(WIP)_ +Write any custom code in your project's `verbfile.js` (like `gulpfile.js` or `Gruntfile.js`). -Methods: +**Heads up!** -- `.create` -- `.loader` -- `.load` -- `.engine` -- `.helper` -- `.helpers` -- `.asyncHelper` -- `.asyncHelpers` -- `.render` +_If you're using Verb's CLI, `verbfile.js` should export an instance of Verb or a function._ -Verb exposes entire API from [template]. See the [template docs] the full API. +**Function** -### Config API +When a function is exported, we refer to these as [verb generators](#generators). Generators can be locally or globally installed, and can consist of entirely custom code, or created using other generators and plugins like building blocks. -**Transforms** +```js +module.exports = function(app) { + // "app" is an instance of verb created by Verb's CLI for this file +}; +``` -Run immediately during init. Used to extend or modify the `this` object. +**Instance** ```js -verb.transform('engine', function() { - this.engine('md', require('engine-lodash')); -}); +var verb = require('verb'); +var app = verb(); + +module.exports = app; ``` -**Application Settings** +## Config -> Set arbitrary values on `verb.cache`: +Options can be defined in `package.json` on the `verb` object. -- `.set` -- `.get` -- `.del` +**Example** -See the [config-cache docs] the full API. +Specify special data to be used in templates for a project: + +```json +{ + "name": "my-project", + "verb": { + "data": { + "twitter": "jonschlinkert" + } + } +} +``` -**Options** +See the [config docs](docs/config.md) for more details. -> Set and get values from `verb.options`: +## Command line -- `.option` -- `.enable` -- `.enabled` -- `.disable` -- `.disabled` -- `.disabled` +### Built-in tasks -See the [option-cache docs] the full API. +Verb only has a few built-in [tasks](docs/tasks.md), but these will externalized to [generators](docs/generators.md) in the near future. -_(WIP)_ +#### list -### Data API +List currently installed verb [generators](docs/generators.md). -> Set and get values from `verb.cache.data` +```js +$ verb new +``` -- `.data` +#### new -Verb exposes entire API from [plasma]. See the [plasma docs] the full API. +Generate a new [verbfile.js](docs/verbfile.js) in the current working directory. -_(WIP)_ +```js +$ verb new +``` -### Middleware API +### Config flags -Verb exposes the entire [en-route] API. See the [en-route docs] the full API. +There are a few flags dedicated to setting and persisting configuration settings. -_(WIP)_ +**Flag** | **Type** | **Scope** | **Description** | **Location** +--- | --- | --- +`--option` | In-memory | Global | Set an option to use in the current session | N/A +`--data` | In-memory | Global | Add data to the context for rendering templates in the current session | N/A +`--config` | Persisted | Project | Persist a configuration setting _to use on the current project_ every time `verb` is run | `~/verb-globals.json` +`--set` | Persisted | Persist a configuration setting _to use on every project_ every time `verb` is run | The `verb` object in package.json -### Task API -### [.task](index.js#L114) +### Config examples -Define a Verb task. +#### Settings -**Params** +Persist configuration settings to the `verb` object in `package.json`. -* `name` **{String}**: Task name -* `fn` **{Function}** +```sh +$ verb --config= +``` -**Example** +**Related links** -```js -verb.task('docs', function() { - verb.src(['.verb.md', 'docs/*.md']) - .pipe(verb.dest('./')); -}); +Persist a list of project names to generate [related links][helpers]{helper-related}: + +```sh +$ verb --config=related.list:generate,assemble,update ``` -### [.watch](index.js#L180) +Updates the `verb` object in package.json with the following: + +```json +{ + "verb": { + "related": { + "list": [ + "generate", + "assemble", + "update" + ] + } + } +} +``` -Re-run the specified task(s) when a file changes. +#### Tasks -**Params** +Specify tasks to run on a project: -* `glob` **{String|Array}**: Filepaths or glob patterns. -* `fn` **{Function}**: Task(s) to watch. +```sh +$ verb --config=tasks: +``` **Example** -```js -verb.task('watch', function() { - verb.watch('docs/*.md', ['docs']); -}); +Automatically run [verb-generate-readme][] on a project: + +```sh +$ verb --config=tasks:readme +# or, specify an array of tasks +$ verb --config=tasks:foo,bar,baz ``` -{%= apidocs("index.js") %} +Updates the `verb` object in package.json with the following: + +```json +{ + "verb": { + "tasks": ["readme"] + } +} +``` + +### Other flags + +#### --init + +Initialize a `verb` config object in package.json of the current project. + +```sh +$ verb --init +``` + +#### --save + +Persist options values to the global data store for `app` ([verb][], [assemble][], [generate][], [update][], etc) + +```sh +$ verb --save +``` -## Related projects -{%= related(verb.related.list, {remove: name}) %} +Most of the above CLI commands can be prefixed with `--save` to persist the value to the global config store. -## Why use Verb? -{%= docs("sections/why.md") %} +#### --file -## Running tests -{%= include("tests") %} +Specify the file to use instead of `verbfile.js`. + +```sh +$ verb --file +``` + +**Example** -## Contributing -{%= include("contributing") %} +```sh +$ verb --file foo.js +``` -## Troubleshooting +#### --cwd -1. First things first, please make sure to run `npm cache clear`, then do `npm i verb verb-cli -g`. If that doesn't clear things up, try #2. -2. Create [an issue]({%= bugs.url %}). We'd love to help, so please be sure to provide as much detail as possible, including: - - version of verb and verb-cli - - platform - - any error messages or other information that might be useful. +Specify the cwd to use +```sh +$ verb --cwd= +``` -## Major changes -- `v0.4.0`: Verb now requires [verb-cli] to run. See the [getting started](#getting-started) section for details. +**Example** -## Author -{%= include("author") %} +```sh +$ verb --cwd="foo/bar" +``` -## License -{%= copyright({start: 2014, linkify: true}) %} -{%= license({linkify: true}) %} +Display the currently defined cwd: -*** +```sh +$ verb --cwd +``` -{%= include("footer") %} -[verb-cli]: https://github.com/verbose/verb-cli +## API +{%= apidocs("index.js") %} -{%= reflinks(verb.reflinks.list) %} \ No newline at end of file +## Upgrading +{%= include("upgrading") %} + + +[verb-generate-readme]: https://github.com/verbose/verb-generate-readme +[generate]: https://github.com/generate/generate +[verb]: https://github.com/verbose/verb +[verbose]: https://github.com/verbose +[helpers]: https://github.com/helpers/ +[github]: https://github.com/ +[gulp-plugins]: https://www.npmjs.com/browse/keyword/gulpplugin \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 05105c9e..5c817ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Release history -## v0.9 (pending) +Starting with 0.9.0, verb will follow [keep-a-changelog][] conventions for tracking release history. -- `body` tag delimiters have changed. the layout tag is now `{{body}}` (instead of `<<% body %>>`) +## key + +Changelog entries are classified using the following labels _(from [keep-a-changelog][]_): + +- `added`: for new features +- `changed`: for changes in existing functionality +- `deprecated`: for once-stable features removed in upcoming releases +- `removed`: for deprecated features removed in this release +- `fixed`: for any bug fixes + +## [Unreleased] + +- `body` tag delimiters have changed. the layout tag is now `{%% body %}` (instead of `<<% body %>>`) + + +[Unreleased]: https://github.com/generate/generate/compare/0.9.0...HEAD +[0.9.0]: https://github.com/generate/generate/compare/0.8.0...0.9.0 + +[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog diff --git a/LICENSE b/LICENSE index fa30c4cb..7cccaf9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2015, Jon Schlinkert. +Copyright (c) 2015-present, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md deleted file mode 100644 index 93d4c882..00000000 --- a/README.md +++ /dev/null @@ -1,314 +0,0 @@ -# verb [![NPM version](https://badge.fury.io/js/verb.svg)](http://badge.fury.io/js/verb) [![Build Status](https://travis-ci.org/verbose/verb.svg)](https://travis-ci.org/verbose/verb) - -> Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes. - -**Built by verb** - -The follow projects use verb to build the reamde and other docs: - -* [micromatch](https://github.com/jonschlinkert/micromatch/) (1.7m downloads/mo) - this readme is pretty extensive, with a TOC and other advanced features -* [is-glob](https://www.npmjs.com/package/is-glob) (1.6m downloads/mo) - example of simple readme -* [repeat-string](https://www.npmjs.com/package/is-glob) (2.2m downloads/mo) - example of another basic readme. - -**Quickstart** - -Install `verb` and `verb-cli` globally: - -```sh -$ npm i verb verb-cli -g -``` - -Next, just add a `.verb.md` markdown template to your project and run `verb` in the commandline _(**NOTE** that verb will overwrite the existing `README`, so make sure your work is committed!)_. - -I'm working on a site for verb, but in the meantime a good place to see `.verb.md` examples is to surf [my projects](https://github.com/jonschlinkert). - -## Install - -Install with [npm](https://www.npmjs.com/) - -```sh -$ npm i verb --save-dev -``` - -## Usage - -```js -var verb = require('verb'); -``` - -## Table of contents - - - -* [Features](#features) -* [CLI](#cli) -* [API](#api) - - [Template API](#template-api) - - [Config API](#config-api) - - [Data API](#data-api) - - [Middleware API](#middleware-api) - - [Task API](#task-api) - - [](#-task--indexjs-l114-)[.task](index.js#L114) - - [](#-watch--indexjs-l180-)[.watch](index.js#L180) - -* [Related projects](#related-projects) -* [Why use Verb?](#why-use-verb-) -* [Running tests](#running-tests) -* [Contributing](#contributing) -* [Troubleshooting](#troubleshooting) -* [Major changes](#major-changes) -* [Author](#author) -* [License](#license) - -_(Table of contents generated by [verb])_ - - - -## Features - -* Generate markdown docs, or HTML -* Generate a Table of Contents simply by adding `` to any document. -* Include templates from locally installed npm packages with the `{%= include() %}` helper -* Include templates from your project's `docs/` directory with the `{%= docs() %}` helper -* Change the templates directory for either helper by passing a `cwd` to the helper: example: `{%= docs("foo", {cwd: ''}) %}` - -## CLI - -_(WIP)_ - -## API - -> Verb's API is organized into the following categories: - -* [Template API](#template-api) -* [Config API](#config-api) -* [Data API](#data-api) -* [Middleware API](#middleware-api) -* [Task API](#task-api) - -### Template API - -_(WIP)_ - -Methods: - -* `.create` -* `.loader` -* `.load` -* `.engine` -* `.helper` -* `.helpers` -* `.asyncHelper` -* `.asyncHelpers` -* `.render` - -Verb exposes entire API from [template](https://github.com/jonschlinkert/template). See the [template docs] the full API. - -### Config API - -**Transforms** - -Run immediately during init. Used to extend or modify the `this` object. - -```js -verb.transform('engine', function() { - this.engine('md', require('engine-lodash')); -}); -``` - -**Application Settings** - -> Set arbitrary values on `verb.cache`: - -* `.set` -* `.get` -* `.del` - -See the [config-cache docs] the full API. - -**Options** - -> Set and get values from `verb.options`: - -* `.option` -* `.enable` -* `.enabled` -* `.disable` -* `.disabled` -* `.disabled` - -See the [option-cache docs] the full API. - -_(WIP)_ - -### Data API - -> Set and get values from `verb.cache.data` - -* `.data` - -Verb exposes entire API from [plasma](https://github.com/jonschlinkert/plasma). See the [plasma docs] the full API. - -_(WIP)_ - -### Middleware API - -Verb exposes the entire [en-route] API. See the [en-route docs] the full API. - -_(WIP)_ - -### Task API - -### [.task](index.js#L114) - -Define a Verb task. - -**Params** - -* `name` **{String}**: Task name -* `fn` **{Function}** - -**Example** - -```js -verb.task('docs', function() { - verb.src(['.verb.md', 'docs/*.md']) - .pipe(verb.dest('./')); -}); -``` - -### [.watch](index.js#L180) - -Re-run the specified task(s) when a file changes. - -**Params** - -* `glob` **{String|Array}**: Filepaths or glob patterns. -* `fn` **{Function}**: Task(s) to watch. - -**Example** - -```js -verb.task('watch', function() { - verb.watch('docs/*.md', ['docs']); -}); -``` - -### [.src](index.js#L60) - -Glob patterns or filepaths to source files. - -**Params** - -* `glob` **{String|Array}**: Glob patterns or file paths to source files. -* `options` **{Object}**: Options or locals to merge into the context and/or pass to `src` plugins - -**Example** - -```js -verb.src('src/*.hbs', {layout: 'default'}) -``` - -### [.dest](index.js#L76) - -Specify a destination for processed files. - -**Params** - -* `dest` **{String|Function}**: File path or rename function. -* `options` **{Object}**: Options and locals to pass to `dest` plugins - -**Example** - -```js -verb.dest('dist') -``` - -### [.copy](index.js#L95) - -Copy a `glob` of files to the specified `dest`. - -**Params** - -* `glob` **{String|Array}** -* `dest` **{String|Function}** -* `returns` **{Stream}**: Stream, to continue processing if necessary. - -**Example** - -```js -verb.task('assets', function() { - verb.copy('assets/**', 'dist'); -}); -``` - -### [.diff](index.js#L116) - -Display a visual representation of the difference between two objects or strings. - -**Params** - -* `a` **{Object|String}** -* `b` **{Object|String}** -* `methodName` **{String}**: Optionally pass a [jsdiff](https://github.com/nathan7/jsdiff)method name to use. The default is `diffJson` - -**Example** - -```js -var doc = verb.views.docs['foo.md']; -verb.render(doc, function(err, content) { - verb.diff(doc.orig, content); -}); -``` - -## Related projects - -* [assemble](https://www.npmjs.com/package/assemble): Static site generator for Grunt.js, Yeoman and Node.js. Used by Zurb Foundation, Zurb Ink, H5BP/Effeckt,… [more](https://www.npmjs.com/package/assemble) | [homepage](http://assemble.io) -* [composer](https://www.npmjs.com/package/composer): The build system used to create verb, assemble and generate. | [homepage](https://github.com/jonschlinkert/composer) -* [engine](https://www.npmjs.com/package/engine): Template engine based on Lo-Dash template, but adds features like the ability to register helpers… [more](https://www.npmjs.com/package/engine) | [homepage](https://github.com/jonschlinkert/engine) -* [template](https://www.npmjs.com/package/template): Render templates using any engine. Supports, layouts, pages, partials and custom template types. Use template… [more](https://www.npmjs.com/package/template) | [homepage](https://github.com/jonschlinkert/template) - -## Why use Verb? - -It's magical and smells like chocolate. If that's not enough for you, it's also the most powerful and easy-to-use documentation generator for node.js. And it's magical. - -## Running tests - -Install dev dependencies: - -```sh -$ npm i -d && npm test -``` - -## Contributing - -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/verbose/verb/issues/new). - -## Troubleshooting - -1. First things first, please make sure to run `npm cache clear`, then do `npm i verb verb-cli -g`. If that doesn't clear things up, try #2. -2. Create [an issue](https://github.com/verbose/verb/issues). We'd love to help, so please be sure to provide as much detail as possible, including: - -* version of verb and verb-cli -* platform -* any error messages or other information that might be useful. - -## Major changes - -* `v0.4.0`: Verb now requires [verb-cli](https://github.com/verbose/verb-cli)to run. See the [getting started](#getting-started) section for details. - -## Author - -**Jon Schlinkert** - -+ [github/jonschlinkert](https://github.com/jonschlinkert) -+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) - -## License - -Copyright © 2014-2015 [Jon Schlinkert](https://github.com/jonschlinkert) -Released under the MIT license. - -*** - -_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on August 21, 2015._ diff --git a/bin/verb.js b/bin/verb.js new file mode 100755 index 00000000..aad86512 --- /dev/null +++ b/bin/verb.js @@ -0,0 +1,100 @@ +#!/usr/bin/env node + +process.env.GENERATE_CLI = true; +process.on('exit', () => { + require('set-blocking')(true); +}); + +const util = require('util'); +const Verb = require('..'); +const commands = require('../lib/commands'); +const tasks = require('../lib/tasks'); +const utils = require('../lib/utils'); +const args = process.argv.slice(2); +const argv = require('yargs-parser')(args); + +/** + * Handle errors + */ + +const handleErr = (app, err) => { + if (app && app.base.hasListeners('error')) { + app.base.emit('error', err); + } else { + console.log(err.stack); + process.exit(1); + } +}; + +/** + * Listen for errors on all instances + */ + +Verb.on('generate.preInit', app => { + app.on('error', err => { + console.log(err.stack); + process.exit(1); + }); +}); + +/** + * Initialize CLI + */ + +Verb.on('generate.postInit', app => { + if (app.macros.has(args)) { + app.macros.set(args); + const macro = {}; + macro[args[0]] = args.slice(2).join(' '); + console.log('saved macro:', util.inspect(macro)); + process.exit(); + } + + const idx = utils.firstIndex(args, ['-D', '--default']); + if (idx !== -1) { + const del = args.indexOf('--del') !== -1; + if (del) { + app.base.store.del('defaultTask'); + } else { + args.splice(idx, 1); + app.base.store.set('defaultTask', args); + } + } +}); + +/** + * Initialize Runner + */ + +const options = { name: 'verb' }; + +utils.runner(Verb, options, argv, (err, app, runnerContext) => { + if (err) handleErr(app, err); + + app.set('cache.runnerContext', runnerContext); + commands(app, runnerContext); + + if (!app.generators.defaults) { + app.register('defaults', require('../lib/generator')); + } + + const ctx = utils.extend({}, runnerContext); + const config = app.get('cache.config') || {}; + ctx.argv.tasks = []; + + app.config.process(config, (err, config) => { + if (err) return handleErr(app, err); + + app.base.cache.config = config; + + app.cli.process(ctx.argv, err => { + if (err) return handleErr(app, err); + + const arr = tasks(app, ctx, argv); + app.log.success('running tasks:', arr); + app.generate(arr, err => { + if (err) handleErr(app, err); + }); + }); + }); +}); diff --git a/docs/_templates/about.md b/docs/_templates/about.md deleted file mode 100644 index ab4fa91f..00000000 --- a/docs/_templates/about.md +++ /dev/null @@ -1,4 +0,0 @@ -# About Verb - -Many documentation generators require strict conventions to work. Verb is the opposite. - diff --git a/docs/_templates/api-data.md b/docs/_templates/api-data.md deleted file mode 100644 index f77f9df6..00000000 --- a/docs/_templates/api-data.md +++ /dev/null @@ -1,89 +0,0 @@ -# Data - -> Define load, transform and process data to be passed to templates as context at render time - -This document describes how Verb works with data, and the methods to used for setting and getting data. - -## Table of contents - - - -## Overview - -- Data that will be passed to templates is stored on `verb.cache.data`. - -**verb.data and package.json** - -- At runtime, Verb loads your project's package.json, so the data can be used in templates. -- Data from package.json is stored on `verb.cache.data`. -- Get the `data` object directly using `verb.cache.data`, or with `verb.get('data')`. -- Get values from this object using `verb.cache.data[foo]` or `verb.get('data.foo')`. - -**verb.env** - -- A read-only clone of the data from package.json is stored on `verb._env`. -- You may get values from this object using `verb.env('foo')` - -## API - -### .data - -```js -// pass an object -verb.data({foo: 'bar'}); - -// pass a glob -verb.data('foo/*.json'); -verb.data('foo/*.yml'); -verb.data('foo/*.{json,yml}'); -``` - -### .set / .get - -Set and get arbitrary values on `verb.cache`. - -### .set - -Store a value: - -```js -verb.set('a', 'b'); -verb.get('a'); -//=> 'b' -``` - -Use object paths (dot notation) to set nested values: - -```js -verb.set('a.b.c', 'd'); -verb.get('a'); -//=> {b: {c: 'd'}} -``` - -**Good to know** - -Although the `.set()` and `.get()` methods store values on `verb.cache`, Verb only uses child objects on `verb.cache`, like `verb.cache.data`. You may use this object however you want. - -### .get - -Sugar for `verb.cache[foo]` - - - -Dot notation may also be used for getting values. - -**Dot notation** - -For example, instead of doing `verb.cache.data.repository.url` - -```js -// get the project name -verb.get('data.name'); - -// or to get the unmodified name from `_env` -verb.env('name'); -``` - -## package.json - -Along with other sources of data, Verb uses data from package.json to render templates. diff --git a/docs/_templates/api-middleware.md b/docs/_templates/api-middleware.md deleted file mode 100644 index fdc1600e..00000000 --- a/docs/_templates/api-middleware.md +++ /dev/null @@ -1,66 +0,0 @@ -# Middleware - -> Methods for defining and using middleware and routes - -**What is "middleware"?** - -A middleware is a function that is invoked by a middleware handler. - -**What is a "handler"?** - -Middleware handlers are invoked at pre-determined points during runtime, each of which is typically associated with a specific middleware method known as a "verb" that will only be invoked by that handler. - -For example, the `.onLoad()` method is invoked by the `onLoad` handler when templates are loaded, and the `.preRender()` method is invoked by the `preRender` handler before templates are passed to the engine for rendering. - -**What does the handler do?** - -When a handler is invoked - -**Middleware stack** - -A middleware stack is an array of middlewares. - -- Each file that passes through the [pipeline](./api-task.md#pipeline.md) has its own middleware stack, which may have zero or more middleware functions -- Middleware functions are always invoked in the order in which they are defined. - - -**Routes** - -Given that "verbs" determine **when** middleware - - -**Verbs** - -> verbs determine **when to run** - -- Special middleware methods known as "verbs" determine **when** middleware functions are run -- Each verb is invoked by a middleware _handler_ that is configured to run that specific verb, at a specic point during runtime. For example, the `onLoad` handler invokes all `.onLoad()` middleware when a template is loaded. - -**Routes** - -> routes determine **which files to operate on** - -- Routes are used to selectively match **which** files to operate on. - - certain triggers - -Middleware is any number of functions that are invoked by the Express.js routing layer before your final request handler is, and thus sits in the middle between a raw request and the final intended route. We often refer to these functions as the middleware stack since they are always invoked in the order they are added. - - -## API - -### .use - -### .route - -### .all - -### .onLoad - -### .preRender` - -### .postRender` - -*** - -## Related topics \ No newline at end of file diff --git a/docs/_templates/api-task.md b/docs/_templates/api-task.md deleted file mode 100644 index fbb588fb..00000000 --- a/docs/_templates/api-task.md +++ /dev/null @@ -1,4 +0,0 @@ - - - -## Pipeline \ No newline at end of file diff --git a/docs/_templates/api-template.md b/docs/_templates/api-template.md deleted file mode 100644 index d981fa77..00000000 --- a/docs/_templates/api-template.md +++ /dev/null @@ -1,36 +0,0 @@ -# Templates - -> Define, load, and render templates - -## Table of contents - - - -## API - -### .create - -**Examples** - -- `.page` -- `.partial` -- `.layout` - - -### .loader - - -### .load - - -### .engine - - -### .helper - - -### .render - - -## Related topics - diff --git a/docs/_templates/api.md b/docs/_templates/api.md deleted file mode 100644 index 807a0e7a..00000000 --- a/docs/_templates/api.md +++ /dev/null @@ -1,151 +0,0 @@ -# Verb API - -> Learn about the various components of Verb's API and how everything fits together. - -## Concepts - -Verb's API is a superset of the following concepts: - -- [Template API](#template-api) -- [Config API](#options-api) -- [Data API](#data-api) -- [Middleware API](#middleware-api) -- [Workflow (Task) API](#workflow-api) - - -## Developer summary - -- **Template API**: methods for loading, caching and rendering, like `.loader()`, `.load()`, `.page()`, `.partial()`, `.layout()`, `.render()`, `.engine()`, `.helper()` etc. -- **Config API**: methods for setting options, `.option()`, `.enable()`, `.disable()` -- **Data API**: methods for loading, and caching data to be passed to templates, like `.data()`, `.transform()` -- **Middleware API**: methods related to routes and middleware, like `.use()`, `.preRender()` etc -- **Workflow (Task) API**: methods for reading and writing to disk, and watching files: `.src()`, `.dest()`, `.task()`, `.watch()`. Without doing math, I'm guessing this is less than 10% of the API - -## Config API - -> Get and set globally available options and config values - -Verb's Config API exposes methods for setting and getting configuration values that can be used anywhere in your verb application. - -**Summary** - -- Options are stored on the `verb.options` object. - - -**Options Methods** - -Options are stored on the `verb.options` object. - -- `.option` -- `.enable` and `.enabled` -- `.disable` and `.disabled` - -**Cache Methods** - -Arbitrary values may be stored on the `verb.cache` object, as long as they do not overwrite Verb's [protected properties][]. - -- `.get` -- `.set` - -The `.get()` and `.set()` methods are used for getting and setting values on the `verb.cache` object. This object is "reserved" for you to store whatever values you need. - -**Usage** - -```js -verb.set('drink', 'Tea, Earl Grey, hot.'); - -var drink = verb.get('drink'); -//=> 'Tea, Earl Grey, hot.' -``` - -## Template API - -> Define, load and render templates - -- `.create` -- `.page` -- `.partial` -- `.layout` -- `.engine` -- `.helper` -- `.render` - - -## Data API - -> Define load, transform and process data to be passed as context to templates at runtime - -- `.data` -- `.transform` - - -## Middleware API - -- `.use` -- `.route` -- `.all` -- `.onLoad` -- `.preRender` -- `.postRender` - - -## Libraries used - -> TODO - -- **Workflow API** - - + vinyl, vinyl-fs: file format - + orchestrator: `.task`, `.src`, `.dest` methods - -- **Config API** - - + options-cache - -- **Data API** - - + plasma - -- **Template API** - - + template - + helper-cache - + engine-cache - + loader-cache - -- **Middleware API** - - + en-route (route) - - -## Workflow API - -> Read, copy, process and write files - -The Workflow API handles "the moving parts" of the build process and consists the following methods: - -- `.task` -- `.src` -- `.dest` -- `.watch` - -**Example** - -```js -verb.task('default', function() { - verb.src('templates/*.hbs') - .pipe(verb.dest('templates/*.hbs')) -}); -``` - -**Learn more** - -- [defining-tasks](./defining-tasks.md) -- [sessions](./task-sessions.md) (TODO: @doowb) - - -**Related** - -- [plugins](./authoring-plugins.md) -- [protected properties](./protected-properties.md) - diff --git a/docs/_templates/built-in-helpers.md b/docs/_templates/built-in-helpers.md deleted file mode 100644 index 39e079c9..00000000 --- a/docs/_templates/built-in-helpers.md +++ /dev/null @@ -1,94 +0,0 @@ -# Built-in helpers - -> Verb ships with more than 100 of built-in template helpers, geared towards making it as easy as possible to build your documentation. - -## Helper collections - -- [console](#console) -- [template-helpers](#template-helpers) -- [markdown-utils](#markdown-utils) -- [readme helpers](#readme-helpers) - -### [console] - -Node.js's [console methods][console] are exposed as helpers. - -**Helper example** - -```js -{%%= console.log("foo") %} -``` - -### [template-helpers] - -All helpers from the [template-helpers] library are available to be used in your templates. - -**Helper example** - -```js -{$%= embed("example.js") %} -``` - -Note that the path helpers are exposed on the `path` object to avoid potential conflicts with other commonly-named path helpers, such as `filename`, etc. - -**Example path helper** - -```js -{%%= path.basename(dest.path) %} -``` - -### [markdown-utils] - -All methods from [markdown-utils] expose as helpers on the `mdu` object. - -**Example markdown helper** - -```js -{%%= mdu.link("verb", "https://github.com/verbose/verb", "awesome") %} -//=> [verb](https://github.com/verbose/verb "awesome") -``` - -### readme helpers - -Although these are referred to as readme helpers, they're useful in any kind of documentation. - -- `apidocs`: generate API documentation from opinionated JavaScript code comments. -- `changelog`: generate a markdown formatted changelog -- `codelinks`: _(TODO)_ -- `copyright`: generate a copyright statement from project metadata -- `coverage`: inject the code coverage summary or report generated by [istanbul] `.txt` -- `date`: add formatted dates -- `license`: generate a license statement from project metadata -- `multiToc`: generate a multi-document table of contents - - -**Example Table of Contents helper** - -_(Just use `` to generate a TOC for the current file only)_ - -```js -{%= multiToc("docs/*.md") %} -``` - -Renders something like: - -```markdown -* [one.md](./one.md) -* [two.md](./two.md) -* [three.md](./one.md) -``` - -### generic helpers - -- `read` -- `relative` -- `yaml` - - - -[console]: https://nodejs.org/api/console.html -[template-helpers]: https://github.com/jonschlinkert/template-helpers -[markdown-utils]: https://github.com/jonschlinkert/markdown-utils - - -{%= reflinks(['istanbul']) %} diff --git a/docs/_templates/cli.md b/docs/_templates/cli.md deleted file mode 100644 index 3f264de2..00000000 --- a/docs/_templates/cli.md +++ /dev/null @@ -1,96 +0,0 @@ -# CLI - -> Verb's commands and command line usage - -## Usage - -All commands are preceded by `--`: - -```bash -$ verb --foo -``` - -## Commands - -- `--nocheck`: don't lint or correct template helpers or variables. See [conflicts]. - -### Data store - -Verb offers a few methods for persisting and getting "default" config data from the command line. Any values set from the command line are saved in a JSON file in - -- when a property is set from the command line it will persist - -**Data store commands** - -- [.set](#-set): set a property. -- [.get](#-get): show a property in the command line -- [.del](#-del): delete a property - -`verb.store.data` `verb.cache.data`[1] from the command line. which is the object Verb uses for storing data to be passed as context to templates at render time. - -#### .set - -Set or extend a property on `verb.cache.data`. - -```bash -$ verb --set foo=bar -``` - -**Dot notation** - -Dot-notation may also be used to set values. - -Example: - -```bash -$ verb --set author.name="Jon Schlinkert" -``` - -#### .get - -Show a property from `verb.cache.data` in the command line. - -```bash -$ verb --get foo -``` - -Outputs: - -```bash -$ foo = bar -``` - -**Dot notation** - -Dot-notation may also be used to get values. - -Example: - -```bash -$ verb --get author.name -``` - -Outputs: - -```bash -$ author.name = "Jon Schlinkert" -``` - -#### .del - -Delete a property from `verb.cache.data` - -```bash -$ verb --del foo && verb --get foo -``` -Outputs: - -```bash -$ foo = undefined -``` - -### Other commands - -```bash -$ verb --foo -``` diff --git a/docs/_templates/collections.md b/docs/_templates/collections.md deleted file mode 100644 index 7d09da65..00000000 --- a/docs/_templates/collections.md +++ /dev/null @@ -1,15 +0,0 @@ - -## Notes - -### Collections - - middleware to generate collections from page front-matter - - helpers to use collection data in pages - -### Pagination - - this goes along with collections but should act independently - - plugin to generate new pages to add to the stream based on pagination options - - helpers to use pagination in pages - -### Relative Linking - - helpers to generate relative links between normal pages, collections pages (lists), and pagination pages (navigation). - diff --git a/docs/_templates/config.md b/docs/_templates/config.md deleted file mode 100644 index 6ad50ba0..00000000 --- a/docs/_templates/config.md +++ /dev/null @@ -1,45 +0,0 @@ -# Data - -> Setting and getting data to be used in templates - -## Table of contents - - - -Verb uses data from package.json to render templates, but there are several other ways to add data. This document describes how Verb works with data, and the methods to used for setting and getting data. - -## Overview - -- Options are stored on `verb.options`. - - -## package.json - -> Verb uses data from package.json to render templates, and more... - -Verb passes the entire package.json object to the template engine to be used as context for templates. But sometimes this isn't enough. For example, you might have a template that requires a Twitter or GitHub username. - -### verb object - -If your package.json file has a `verb` property, Verb will use it to extend the context. - -**Example** - -```json -{ - "name": "my-project", - "description": "It's awesome, seriously.", - - "verb": { - "ignore": [ - ".git" - ], - "deps": { - "ignore": [ - "support" - ] - }, - "data": {} - } -} -``` \ No newline at end of file diff --git a/docs/_templates/data.md b/docs/_templates/data.md deleted file mode 100644 index 8aea0a3f..00000000 --- a/docs/_templates/data.md +++ /dev/null @@ -1,37 +0,0 @@ -# Data - -> Setting and getting data to be used in templates - -## Table of contents - - - -Verb uses data from package.json to render templates, but there are several other ways to add data. This document describes how Verb works with data, and the methods to used for setting and getting data. - -## package.json - -> Verb uses data from package.json to render templates, and more... - -Verb passes the entire package.json object to the template engine to be used as context for templates. But sometimes this isn't enough. For example, you might have a template that requires a Twitter or GitHub username. - -### verb object - -If your package.json file has a `verb` object with a `data` property, Verb will use it to extend the context. - -**Example** - -This example shows how you would add your Twitter username to package.json so that it will be used in templates: - -```json -{ - "name": "my-project", - "description": "It's awesome, seriously.", - - "verb": { - "data": { - "twitter": {"username": "jonschlinkert"} - } - } -} -``` -Also see how to set [configuration values](./config.md) in package.json. \ No newline at end of file diff --git a/docs/_templates/engines.md b/docs/_templates/engines.md deleted file mode 100644 index fb3acd8d..00000000 --- a/docs/_templates/engines.md +++ /dev/null @@ -1,91 +0,0 @@ -# Engines - -> Registering and using template engines with Verb. - -Verb's default template engine is [engine-lodash][], but Verb can use any template engine to render templates. - -## Table of contents - - - -## Overview - -- engines are registered with `verb.engine()` -- when registered, engines are stored as objects on `verb.engines`. -- async engines must follow [consolidate.js][consolidate] conventions, and sync engines must follow [engines.js][engines] conventions. - - -## Register an engine - -Engines are registered using `verb.register()`. - -**Example** - -```js -verb.register('foo', {}, function() {}); -``` - -`.register` takes the following arguments: - -- `ext` **{String}**: once registered, the engine will process any files with this extension. -- `options` **{Object}**: optionally pass an object of options -- `engine` **{Function|Object}**: the engine to be registered (more on [engine format below](#author-an-engine)) - -### Usage examples - -Render handlebars templates in files with the `.hbs` extension: - -**Install** - -```bash -$ npm i consolidate handlebars --save-dev -``` - -**Usage** - -```js -// make sure you have handlebars installed in node_modules too! -var consolidate = require('consolidate'); -verb.engine('.hbs', consolidate.handlebars); -``` - -Render Lo-Dash templates in files with the `.tmpl` extension: - -**Install** - -```bash -$ npm i engine-lodash --save-dev -``` - -**Usage** - -```js -verb.engine('.tmpl', require('engine-lodash')); -``` - -## Author an engine - -> Engines are just javascript functions that transform a string - -Engines can be a function, or an object with `.render()` and/or `.renderSync()` methods. - -**Example** - -The most basic engine is a function that takes the following paramters: - -- `str`: the string to process -- `options` which will be passed to the engine as context/locals -- `cb`: callback function - -```js -var coffee = require('coffee-script'); - -verb.engine('coffee', {ext: '.js'}, function(str, opts, cb) { - try { - cb(null, coffee.compile(str, opts)); - } catch(err) { - return cb(err); - } -}); -``` - diff --git a/docs/_templates/faq.md b/docs/_templates/faq.md deleted file mode 100644 index 43ef856d..00000000 --- a/docs/_templates/faq.md +++ /dev/null @@ -1,3 +0,0 @@ -# FAQ - -> Frequently asked questions diff --git a/docs/_templates/features.md b/docs/_templates/features.md deleted file mode 100644 index 5141e215..00000000 --- a/docs/_templates/features.md +++ /dev/null @@ -1,7 +0,0 @@ - -- Generate API docs from code comments (this couldn't be easier!) -- Add a [.verb.md](#verbmd) markdown template to your project, and verb will build your readme using data from package.json. -- If you need more, create a [verbfile.js](#verbfile.js)! Verb can complex documentation too, including multi-page TOCs, cross-reference links, auto-generated links to dependencies and so on. -- Verb can run any [gulp](https://github.com/gulpjs/gulp) plugin -- Verb is built on top of [Template](https://github.com/jonschlinkert/template). All of Template's methods are exposed on the API. -- You can get by with a simple `.verb.md` markdown template, or do things like add layouts, pages, partials, helpers, register a template engine, load data or use a `.transform()` or two to modify that data at runtime. diff --git a/docs/_templates/getting-started.md b/docs/_templates/getting-started.md deleted file mode 100644 index 001a4f72..00000000 --- a/docs/_templates/getting-started.md +++ /dev/null @@ -1,32 +0,0 @@ -# Getting started - -> First steps to start using verb - -## First run - -The first time you run verb you will be asked to provide two or three pieces of data so that verb has the necessary data to render your project's templates. - -**Questions you'll be asked** - -- `What's your name?` -- `What's your GitHub username?` -- `What's your Twitter username?` - -You can skip any of these, but Verb may not be able to render certain templates without this information. - -## verb init - -Repeat the [first run](#first-run) questions: - -```sh -verb --init -``` - -## Recommended variables - -Although verb only asks for three values, it's recommended add a few other commonly used variables as well. - -_(TODO)_ - -See the documentation for [cli commands](./cli.md), including [verb `--set`]('./cli.md#set') if you aren't sure how to set variables. - diff --git a/docs/_templates/helper-conflict-resolution.md b/docs/_templates/helper-conflict-resolution.md deleted file mode 100644 index 810902c3..00000000 --- a/docs/_templates/helper-conflict-resolution.md +++ /dev/null @@ -1,133 +0,0 @@ -# Helper conflict resolution - -> This describes how helper conflict resolution works in Verb, as well as **why it works this way** - -_(If you have any suggestions for doing this differently, please let us know!)_ - -**What's a "helper conflict"?** - -Helper conflicts occurr when all of the following conditions are met: - -1. A helper function is registered with the same name as an arbitrary property on the context, and -2. The template engine being used does not have first class support for helper functions -3. The user opts to _not_ namespace the data on some arbitrary property - -_(Note that if namespacing is used consistently across all projects, like using `foo.name` instead of just `name`, then these issues go away. However, [namespacing presents its own set challenges][1].)_ - -**What is "first class" helper support?** - -First, some background. Verb must pass templates, partials, data and helper functions to engines in a way that the engine understands. Some engines, like Handlebars, support all of these concepts as separate, first-class entities. Which makes life easy and provides (non-hacky) options for keeping data, helpers and templates cleanly separated. It also dramatically reduces the logic required to handle each of these concepts. We'll call these engines Type 1 engines. Other engines, like Lo-Dash, only understand templates and data. We'll call these Type 2 engines. Neither is better or worse. - -An advantage of Type 2 engines is that they are usually faster and, given that they have no real conventions for helpers, helpers are just plain javascript, which makes them (marginally) easier to write. - -Verb's default engine is [engine-lodash], a Type 2 engine. Given that Type 2 engines have no first class support for helpers, helpers are treated the same as any other data and are passed as arbitrary properties on the context. If a property on the context happens to have a function value, the only special treatment it gets is that the engine simply calls the function (helper) in the given context. Still, it's just another property on the context. - -The same applies to partials. Partials are also passed as arbitrary properties on the context. - -**The challenge** - -What makes this a sticky situation is that we need to adhere to the rules of JavaScript: Object keys must be unique. - -**Example** - -By way of example, let's see what happens when the following occurs: - -```js -// `context` and `helpers` are passed to an engine -// on the engine's `options` object - -var options = {context: {foo: 'bar'}, helpers: {foo: [function]}} -``` - -At render-time, these objects are passed to the engine separately, then, since the engine needs all of these values on one object (as context), the (Type 2) engine merges them together just before rendering a template. (This is something an implementor would need to do when implementing an engine. Feel free to follow the pattern used by [engine-lodash]). - -**Who should win: data or helper?** - -Continuing with the above code example, what should we end up with when we merge the `helpers` and `context` objects? - -```js -var ctx = _.extend({}, options.helpers, options.context); -//=> ctx.foo === 'bar' ??? -``` -Or: - -```js -var ctx = _.extend({}, options.context, options.helpers); -//=> ctx.foo === [function] ??? -``` - -And which of the following should the template engine give priority to? - -```js -// non-function value -{%%= foo %} - -// helper function -{%%= foo("quux") %} -``` - -You might be wondering: - -**But, how often does this happen?** - -The short answer is: a lot. The long answer is: a looooooooot. - -**Isn't this just an edge case?** - -No, it happens a lot. In other words, it happens more often than not a lot. - -_(Just seeing if you're paying attention :)_ - -## Solution: namespacing on-the-fly - -Hack or not, of all the solutions we've tried we find this to be the easiest to maintain. - -**Conflict resolution middleware** - -Verb's [conflict-resolution middleware][] does the following: - -1. Gets keys of all properties on the `verb.cache.data` object -2. Scans templates for helpers that use those properties -3. When a helper is found with a name that matches a property on the context: - + the helper function is moved from `verb._.helpers.foo` to `verb._.helpers.__.foo` - + the helper is renamed in the string using the `__.` prefix (see below) - -**Resulting in the following** - -The final context object ends up like this: - -```js -var ctx = _.extend({}, options.context, options.helpers); -//=> {foo: 'bar', __: {foo: [function]}} -``` - -And the helper is renamed as follows - -```js -// from this -{%%= foo("quux") %} - -// to this -{%%= __.foo("quux") %} -``` - -Now, both of the following templates will resolve: - -```js -{%%= foo %} -{%%= __.foo("quux") %} -``` - -**Done!** - -To the user, this is transparent. You would never even know it was happening unless you inspected the string after the `.preRender()` middleware is called. Benchmarks showed a 2 millisecond difference when conflicting helpers were updated in ten templates. Clearly this would compound, but in our opinion it's a very acceptable compromise if your using Verb on a small project or just using verb to build a readme. - -## Notes - -#### 1 - -As it relates specifically to the **auto-generated data in Verb**, the challenge with namespacing is that it dramatically increases the amount of logic necessary to normalize data that would otherwise be on a single object. - -For example, there are several transforms and middleware that normalize package.json values. Namespacing with arbitrary objects necessitates far more logic to provide the same user experience. - -More so, I=if we were to, say, allow the user to define a custom property to use, then templates quickly become much less likely to be re-usable and transferrable across projects. diff --git a/docs/_templates/helpers.md b/docs/_templates/helpers.md deleted file mode 100644 index ec760ec1..00000000 --- a/docs/_templates/helpers.md +++ /dev/null @@ -1,26 +0,0 @@ -# Helpers - -> Registering and using helpers with Verb. - -Helpers are used in templates to transform data, include other templates, or do anything else that can be done with JavaScript _inside templates_. - -## Overview - -- generated helpers -- built-in helpers -- user-defined helpers - - -**Built-in helpers** - -By default, Verb uses [engine-lodash][], so the helpers we use by default are really just javascript functions that we pass to Lo-Dash on the context at render time. - - -## Verb's defaults - -Helpers are used when data needs to be updated dynamically, or when it needs to be calculated - - -## Related - -- [Helper conflict resolution](./helper-conflict-resolution.md) diff --git a/docs/_templates/helpers/apidocs.md b/docs/_templates/helpers/apidocs.md deleted file mode 100644 index 386e7c98..00000000 --- a/docs/_templates/helpers/apidocs.md +++ /dev/null @@ -1,29 +0,0 @@ -# apidocs - -> Generate API documentation from code comments using the apidocs helper. - -This is about using and customizing the `apidocs` helper. - - - -## Custom template - -> Define a custom template to use - -Pass the path to your template on the `template` option. - -In verb, you can set the path the following ways: - -**API** - -```js -verb.option('apidocs', {template: './path/to/template.js'}); -``` - -**CLI** - -Store the path or module name on the global config: - -```sh -$ verb --set apidocs.template="./path/to/template.js" -``` \ No newline at end of file diff --git a/docs/_templates/includes.md b/docs/_templates/includes.md deleted file mode 100644 index 74f77d92..00000000 --- a/docs/_templates/includes.md +++ /dev/null @@ -1,53 +0,0 @@ -# Includes - -## author - -> Add an `## Author` section - -**Example** - -```markdown -## Author -{%%= include("author") %} -``` - -**Result** - -```markdown -## Author - -**Jon Schlinkert** - -+ [github/jonschlinkert](https://github.com/jonschlinkert) -+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) -``` - -**Twitter** - -```js -{%%= include("author") %} -{%%= include("author", {username: 'jonschlinkert'}) %} -{%%= include("author", {twitter: 'jonschlinkert'}) %} -{%%= include("author", {twitter: {username: 'jonschlinkert'}}) %} -``` - -**GitHub** - -```js -{%%= include("author") %} -{%%= include("author", {username: 'jonschlinkert'}) %} -{%%= include("author", {github: 'jonschlinkert'}) %} -{%%= include("author", {github: {username: 'jonschlinkert'}}) %} -``` - -**Both** - -```js -{%%= include("author") %} -{%%= include("author", {username: 'jonschlinkert'}) %} -{%%= include("author", {github: 'jonschlinkert', twitter: 'jonschlinkert'}) %} -{%%= include("author", { - github: {username: 'jonschlinkert'}, - twitter: {username: 'jonschlinkert'} -}) %} -``` \ No newline at end of file diff --git a/docs/_templates/middleware.md b/docs/_templates/middleware.md deleted file mode 100644 index 8d657f9d..00000000 --- a/docs/_templates/middleware.md +++ /dev/null @@ -1,33 +0,0 @@ -# Middleware - -> Using routes and middleware with Verb - -## Overview - - -## VERBS - -### .onLoad - -**Handles** - -- partials -- layouts -- renderable templates - - -### .preRender - -**Handles** - -- renderable templates only - - -### .postRender - -**Handles** - -- renderable templates only - - -## Pro Tips diff --git a/docs/_templates/options.md b/docs/_templates/options.md deleted file mode 100644 index 115a0084..00000000 --- a/docs/_templates/options.md +++ /dev/null @@ -1,31 +0,0 @@ -# Options - -> Setting and getting options - -## Table of contents - - - -## Overview - -- Options are stored on `verb.options`. - - -### package.json - -> setting options in package.json - -If your package.json file has a `verb` property with an `options` object, Verb will use it to extend the `verb.options` object. - -**Example** - -```json -{ - "name": "my-project", - "description": "It's awesome, seriously.", - - "verb": { - "options": {} - } -} -``` \ No newline at end of file diff --git a/docs/_templates/overview.md b/docs/_templates/overview.md deleted file mode 100644 index 74e540dd..00000000 --- a/docs/_templates/overview.md +++ /dev/null @@ -1,52 +0,0 @@ -# Verb Overview - -> Stuff you might find useful to know about Verb. - -## Setting variables - -### inside a document - -If you need to override a variable on the context, you can set variables directly inside a document. In fact, there are multiple ways to do this: - -**Front matter** - -```html ---- -foo: "bar" ---- - -{%%= foo %} -//=> "bar" -``` - -**Lo-Dash template** - -```js -{%% var foo = "bar" %} -{%%= foo %} -//=> "bar" -``` - -**Helper locals** - -If you're using a helper, you can pass data as the second parameter: - -```js -{%%= include("author", {username: "jonschlinkert"}) %} -``` - -## Includes - -**Headings** - -When using the `docs` or `include` helpers, Verb adds one heading level to the heading levels set in the includes to ensure that they flow with rest of the document. - -So `##` becomes `###`. - -**cwd** - -Change the current working directory (the directory to use for getting templates): - -```js -{%%= include("foo", {cwd: "my-includes"}) %} -``` diff --git a/docs/_templates/plugins/conflicts.md b/docs/_templates/plugins/conflicts.md deleted file mode 100644 index 40c3d60a..00000000 --- a/docs/_templates/plugins/conflicts.md +++ /dev/null @@ -1,12 +0,0 @@ -# Linter plugin - -> Lint templates for missing or conflicting variables and helpers. - -**Disable** - -To disable the plugin: - -```sh -$ verb --disable linter -``` - diff --git a/docs/_templates/protected-properties.md b/docs/_templates/protected-properties.md deleted file mode 100644 index c238d832..00000000 --- a/docs/_templates/protected-properties.md +++ /dev/null @@ -1,12 +0,0 @@ -# Protected properties - -> - -Verb stores certain information on a few objects that should not be overwritten. These properties are: - -- `verb.cache` -- `verb.cache.data` -- `verb.options` - - - diff --git a/docs/_templates/template-data.md b/docs/_templates/template-data.md deleted file mode 100644 index aad2f613..00000000 --- a/docs/_templates/template-data.md +++ /dev/null @@ -1,35 +0,0 @@ -## Template data - -> Load data to pass to templates - - -```js -var verb = require('verb'); -``` - -```js -verb.data('data/*.yml'); -verb.layout('default.md', '\n<<% body %>>\n'); -verb.includes('includes/*.md'); -verb.docs('docs/*.md'); - -verb.helperAsync('docs', function (name, locals, cb) { - var doc = this.cache.docs[name]; - this.render(doc, locals, function (err, content) { - if (err) return cb(err); - cb(null, content); - }); -}); - -verb.task('readme', function() { - verb.src('.verb.md') - .pipe(verb.dest('./')); -}); - -verb.task('docs', function() { - verb.src('docs/_templates/*.md') - .pipe(verb.dest('./wiki')); -}); - -verb.task('default', ['readme', 'docs']); -``` diff --git a/docs/_templates/using-layouts.md b/docs/_templates/using-layouts.md deleted file mode 100644 index 836b932e..00000000 --- a/docs/_templates/using-layouts.md +++ /dev/null @@ -1,21 +0,0 @@ -## Using layouts - -> Layouts are used to wrap other templates with common or project-wide content. - -In your verbfile.js: - -```js -var verb = require('verb'); - -// register a layout with verb. -verb.layout('default.md', {content: '# {%%= name %}\n\n<<%% body %>>\n'}); - -// register a page, and use the layout by adding the `layout` property. -verb.page('api.md', {content: 'API docs...', layout: 'default.md'}); - -//=> '# {%%= name %}\n\nAPI docs...\n' -``` - -**What's with the wierd `body` syntax?** - -Verb is a documentation generator, the crazy `body` syntax is verb's way of ensure that we avoid collision with templates that might be in code examples. diff --git a/docs/_templates/variables.md b/docs/_templates/variables.md deleted file mode 100644 index b64a8cc4..00000000 --- a/docs/_templates/variables.md +++ /dev/null @@ -1,45 +0,0 @@ -# Variables - -> Describes how variables are used in Verb, namespacing, and protected variables. - -- `author` -- `bugs` -- `data` -- `dependencies` -- `deps` -- `description` -- `devDependencies` -- `engines` -- `files` -- `fork` -- `github.username` -- `homepage` -- `ignore` -- `include` -- `keywords` -- `license` -- `main` -- `name` -- `node` -- `repository.url` -- `repository` -- `scripts` -- `test` -- `twitter.username` -- `type` -- `url` -- `username` -- `verb` -- `version` - -## user vs username - -- `user`: The actual owner of the GitHub project -- `username`: Your username - -This distinguishment is important when you're rendering the readme for a project that belongs to an org that _isn't you_. - - -## Protected variables - - diff --git a/docs/_templates/verbfile.md b/docs/_templates/verbfile.md deleted file mode 100644 index fd241be6..00000000 --- a/docs/_templates/verbfile.md +++ /dev/null @@ -1,15 +0,0 @@ -# verbfile - -> Creating a basic verbfile.js - -This is what the default `verbfile.js` looks like: - -```js -var verb = require('verb'); - -verb.task('default', function() { - verb.src('.verb.md') - .pipe(verb.dest('.')); -}); -``` - diff --git a/docs/_templates/verbmd.md b/docs/_templates/verbmd.md deleted file mode 100644 index 6772d44b..00000000 --- a/docs/_templates/verbmd.md +++ /dev/null @@ -1,94 +0,0 @@ -# .verb.md - -> .verb.md is a markdown template that Verb uses to generate your project's readme. - -If your project has a `.verb.md` template, verb will automatically render it using data from your project's package.json file. - -## Example .verb.md - -You can use any variables, helpers, templates or data you want in Verb documentation. This example below just happens to be the template that Verb's maintainers like on their own projects. - -Below we'll describe each section. - -```markdown -# {%%= name %} {%%= badge("fury") %} - -> {%%= description %} - -## Install -{%%= include("install") %} - -## API -{%%= apidocs("index.js") %} - -## Author -{%%= include("author") %} - -## License -{%%= copyright() %} -{%%= license() %} - -*** - -{%%= include("footer") %} -``` - -_(before going further, please read [how verb works](./how-verb-works.md) if you need a primer on how these templates work.)_ - -There are only a couple of template variables used, the rest of example consists of [helpers](./helpers.md). Let's go over all of them. - - -## Variables - -Simple variables, like `name` are typically used when their values can be resolved using data from package.json without doing a lot of work to get the data. - -### name - -Uses the value of the `name` property from package.json. - -```js -{%%= name %} -``` - -Uses the `description` property from package.json. - -```js -> {%%= description %} -``` - -## Helpers - -Helpers are used when data needs to be updated dynamically, or when it needs to be calculated - -### badge - -This is a helper that gets badge templates from node_modules and renders them using data in package.json. There are other badges, like `travis`, but you can add your own if you want or do a pr to verb to add more. - -```js -{%%= badge("fury") %} -``` - -### description - -```js -{%%= include("install") %} -``` - -```js -{%%= apidocs("index.js") %} -``` - -```js -{%%= include("author") %} -``` - -```js -{%%= license() %} -``` -```js -{%%= copyright() %} -``` - -```js -{%%= include("footer") %} -``` \ No newline at end of file diff --git a/docs/sections/_temp.md b/docs/sections/_temp.md deleted file mode 100644 index b85109ba..00000000 --- a/docs/sections/_temp.md +++ /dev/null @@ -1,287 +0,0 @@ -# {%= name %} {%= badge("fury") %} {%= badge("travis") %} - -> {%= description %} - -**Heads up!** - -As of v0.4.0, Verb now requires [verb-cli][] to run. See the [getting started](#getting-started) section for details. - -**Features** - -- Build API docs from code comments (this couldn't be easier!) -- Add a [.verb.md](#verbmd) markdown template to your project, and verb will build your readme using data from package.json. -- If you need more, create a [verbfile.js](#verbfile.js)! Verb can complex documentation too, including multi-page TOCs, cross-reference links, auto-generated links to dependencies and so on. -- Verb can run any [gulp](https://github.com/gulpjs/gulp) plugin -- Verb is built on top of [Template](https://github.com/jonschlinkert/template). All of Template's methods are exposed on the API. -- You can get by with a simple `.verb.md` markdown template, or do things like add layouts, pages, partials, helpers, register a template engine, load data or use a `.transform()` or two to modify that data at runtime. - - -## Install verb-cli - -As of v0.4.0, Verb requires verb-cli to run. To install verb-cli, run: - -```bash -npm i -g verb-cli -``` - -## .verb.md - -Add a `.verb.md.` [template][verbmd] to your project and run `verb` from the command line to generate the project's readme using data from package.json. - -If you need more, use a [verbfile.js][verbfile]. - -**Example .verb.md** - -This is a basic readme template that Verb's own maintainers like to use. - -```markdown -# {%%= name %} {%%= badge("fury") %} - -> {%%= description %} - -## Install -{%%= include("install") %} - -## API -{%%= apidocs("index.js") %} - -## Author -{%%= include("author") %} - -## License -{%%= copyright() %} -{%%= license() %} - -*** - -{%%= include("footer") %} -``` - -`.verb.md` files are rendered using data from `package.json`, but Verb is not restricted to package.json. You can use any data you want, with any templates, helpers, etc. - - -## verbfile.js - -For projects that need more than readme documentation, you can add a `verbfile.js` to the project, and be sure to install verb locally with: - -```bash -npm i verb --save-dev -``` - -**Example basic verbfile.js** - -```js -var verb = require('verb'); - -// load data for templates if needed -verb.data('foo/*.json'); - -verb.task('default', function() { - verb.src(['.verb.md', 'docs/*.md']) - .pipe(verb.dest('./')); -}); -``` - -*** - -# Template API - -> See [Template](https://github.com/jonschlinkert/template) for all available methods. - -### .data - -> Load data to pass to templates. - -Any of these work: - -```js -verb.data({foo: 'bar'}); -verb.data('package.json'); -verb.data(['foo/*.{json,yml}']); -``` - -### .helper - -> Add helpers to be used in templates. - -```js -verb.helper('read', function(filepath) { - return fs.readFileSync(filepath, 'utf8'); -}); - -//=> {%%= read("foo.txt") %} -``` - -### .partial - -> Add partials to be used in other templates. - -```js -verb.partial('notice', { content: '...' }); -verb.partial('banner', { content: '/*! Copyright (c) 2014 Jon Schlinkert, Brian Woodward... */' }); -// or load a glob of partials -verb.partials('partials/*.md'); - -// optionally pass locals, all template types support this -verb.partials('partials/*.md', {site: {title: 'Code Project'}}); -``` - -**Usage** - -Use the `partial` helper to inject into other templates: - -```js -{%%= partial("banner") %} -``` - -Get a cached partial: - -```js -var banner = verb.cache.partials['banner']; -``` - -### .page - -> Add pages that might be rendered (really, any template is renderable, pages fit the part though) - -```js -verb.page('toc.md', { content: 'Table of Contents...'}); -// or load a glob of pages -verb.pages('pages/*.md', {site: {title: 'Code Project'}}); -``` - -Use the `page` helper to inject pages into other templates: - -```js -{%%= page("toc") %} -``` - -Get a cached page: - -```js -var toc = verb.cache.pages['toc']; -``` - -Pages are `renderable` templates, so they also have a `.render()` method: - -```js -var toc = verb.cache.pages['toc']; -// async -toc.render({}, function(err, content) { - console.log(content); -}); - -// or sync -var res = toc.render(); -``` - -**Params** - - - `locals` **{Object}**: Optionally pass locals as the first arg - - `callback` **{Function}**: If a callback is passed, the template will be rendered async, otherwise sync. - - -### .layout - -> Add layouts, which are used to "wrap" other templates: - -```js -verb.layout('default', {content: [ - '', - ' ', - ' ', - ' ', - ' {%%= title %}', - ' ', - ' ', - ' <<% body %>>', // `body` is the insertion point for another template - ' ', - '' -].join('\n')}); - -// or load a glob of layouts -verb.layouts('layouts/*.md', {site: {title: 'Code Project'}}); -``` - -Layouts may be use with any other template, including other layouts. Any level of nesting is also possible. - -**Body tags** - -Layouts use a `body` as the insertion point for other templates. The syntax verb uses for the `body` tag is: - -```js -<<% body %>> -``` - -Admittedly, it's a strange syntax, but that's why we're using it. Verb shouldn't collide with templates that you might be using in your documentation. - - -**Usage** - -Layouts can be defined in template locals: - -```js -// either of these work (one object or two) -verb.page('toc.md', { content: 'Table of Contents...'}, { layout: 'default' }); -verb.partial('foo.md', { content: 'partial stuff', layout: 'block' }); -``` - -Or in the front matter of a template. For example, here is how another layout would use our layout example from earlier: - -```js -// using this 'inline' template format to make it easy to see what's happening -// this could be loaded from a file too -verb.layout('sidebar', {content: [ - '---', - 'layout: default', - '---', - '
', - ' <<% body %>>', - '
' -].join('\n')}); -``` - -# Task API - -{%= apidocs("index.js") %} - - -## Why Verb instead of X? - -I created Verb to help me maintain my own projects. Any time that is spent maintaining a project that could be automated instead, is time that is being taken away from real productivity. - -**Verb does exactly what I needed** - -To that end, I wanted a documentation generator that would work in the following scenarios: - -- **simple, no config**: most projects don't need complicated docs. e.g. "just build the readme and don't ask me questions." -- **handle the boilerplate stuff**: APIs change from project to project, but my name doesn't, my github URL doesn't, and my choice of licence doesn't. Verb should know those things. -- **don't ask me questions**: I just want to run `verb`, and it should work. No setup or config. There is more than enough data in package.json to handle the boilerplate part of a readme. -- **generate API docs**: When I want [API docs](#api-docs), I should have to jump through hoops, or add `.json` files to directories. I should be able to add the docs wherever I want, to the README, separate docs, or use templates to generate a gh-pages site. -- **render markdown, not HTML**: this one was important to me. There are hundreds of [great libs](https://github.com/jonschlinkert/remarkable) that can render markdown to HTML. Once you have well-formatted markdown documentation, it's easy to convert to HTML. - - -## Running tests - -Install dev dependencies: - -```bash -npm test -``` - -## Contributing -Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue]({%= bugs.url %}) - -## Author -{%= include("author") %} - -## License -{%= copyright({year: 2014}) %} -{%= license() %} - -*** - -{%= include("footer") %} - -[verb-cli]: https://github.com/verbose/verb-cli - diff --git a/docs/sections/cli-notice.md b/docs/sections/cli-notice.md deleted file mode 100644 index cfc7e9bd..00000000 --- a/docs/sections/cli-notice.md +++ /dev/null @@ -1,2 +0,0 @@ - -As of v0.4.0, Verb now requires [verb-cli] to run. See the [getting started](#getting-started) section for details. \ No newline at end of file diff --git a/docs/sections/features.md b/docs/sections/features.md deleted file mode 100644 index 547e0761..00000000 --- a/docs/sections/features.md +++ /dev/null @@ -1,5 +0,0 @@ -- Generate markdown docs, or HTML -- Generate a Table of Contents simply by adding `` to any document. -- Include templates from locally installed npm packages with the `{%%= include() %}` helper -- Include templates from your project's `docs/` directory with the `{%%= docs() %}` helper -- Change the templates directory for either helper by passing a `cwd` to the helper: example: `{%%= docs("foo", {cwd: ''}) %}` diff --git a/docs/sections/install-verb-cli.md b/docs/sections/install-verb-cli.md deleted file mode 100644 index 475147e5..00000000 --- a/docs/sections/install-verb-cli.md +++ /dev/null @@ -1,5 +0,0 @@ -As of v0.4.0, Verb requires verb-cli to run. To install verb-cli, run: - -```bash -npm i -g verb-cli -``` \ No newline at end of file diff --git a/docs/sections/overview.md b/docs/sections/overview.md deleted file mode 100644 index 2e893d30..00000000 --- a/docs/sections/overview.md +++ /dev/null @@ -1,192 +0,0 @@ -**Heads up!** -{%= docs("sections/cli-notice") %} - -**Features** -{%= docs("sections/features") %} - -## Install verb-cli -{%= docs("sections/install-verb-cli") %} - -## .verb.md -{%= docs("sections/verb-md") %} - -## verbfile.js -{%= docs("sections/verbfile") %} - -*** - -# Template API - -> See [Template](https://github.com/jonschlinkert/template) for all available methods. - -### .data - -> Load data to pass to templates. - -Any of these work: - -```js -verb.data({foo: 'bar'}); -verb.data('package.json'); -verb.data(['foo/*.{json,yml}']); -``` - -### .helper - -> Add helpers to be used in templates. - -```js -verb.helper('read', function(filepath) { - return fs.readFileSync(filepath, 'utf8'); -}); - -//=> {%%= read("foo.txt") %} -``` - -### .partial - -> Add partials to be used in other templates. - -```js -verb.partial('notice', { content: '...' }); -verb.partial('banner', { content: '/*! Copyright (c) 2014 Jon Schlinkert, Brian Woodward... */' }); -// or load a glob of partials -verb.partials('partials/*.md'); - -// optionally pass locals, all template types support this -verb.partials('partials/*.md', {site: {title: 'Code Project'}}); -``` - -**Usage** - -Use the `partial` helper to inject into other templates: - -```js -{%%= partial("banner") %} -``` - -Get a cached partial: - -```js -var banner = verb.cache.partials['banner']; -``` - -### .page - -> Add pages that might be rendered (really, any template is renderable, pages fit the part though) - -```js -verb.page('toc.md', { content: 'Table of Contents...'}); -// or load a glob of pages -verb.pages('pages/*.md', {site: {title: 'Code Project'}}); -``` - -Use the `page` helper to inject pages into other templates: - -```js -{%%= page("toc") %} -``` - -Get a cached page: - -```js -var toc = verb.cache.pages['toc']; -``` - -Pages are `renderable` templates, so they also have a `.render()` method: - -```js -var toc = verb.cache.pages['toc']; -// async -toc.render({}, function(err, content) { - console.log(content); -}); - -// or sync -var res = toc.render(); -``` - -**Params** - - - `locals` **{Object}**: Optionally pass locals as the first arg - - `callback` **{Function}**: If a callback is passed, the template will be rendered async, otherwise sync. - - -### .layout - -> Add layouts, which are used to "wrap" other templates: - -```js -verb.layout('default', {content: [ - '', - ' ', - ' ', - ' ', - ' {%%= title %}', - ' ', - ' ', - ' <<% body %>>', // `body` is the insertion point for another template - ' ', - '' -].join('\n')}); - -// or load a glob of layouts -verb.layouts('layouts/*.md', {site: {title: 'Code Project'}}); -``` - -Layouts may be use with any other template, including other layouts. Any level of nesting is also possible. - -**Body tags** - -Layouts use a `body` as the insertion point for other templates. The syntax verb uses for the `body` tag is: - -```js -<<% body %>> -``` - -Admittedly, it's a strange syntax, but that's why we're using it. Verb shouldn't collide with templates that you might be using in your documentation. - - -**Usage** - -Layouts can be defined in template locals: - -```js -// either of these work (one object or two) -verb.page('toc.md', { content: 'Table of Contents...'}, { layout: 'default' }); -verb.partial('foo.md', { content: 'partial stuff', layout: 'block' }); -``` - -Or in the front matter of a template. For example, here is how another layout would use our layout example from earlier: - -```js -// using this 'inline' template format to make it easy to see what's happening -// this could be loaded from a file too -verb.layout('sidebar', {content: [ - '---', - 'layout: default', - '---', - '
', - ' <<% body %>>', - '
' -].join('\n')}); -``` - -# Task API - -{%= apidocs("index.js") %} - - -## Why Verb instead of X? - -I created Verb to help me maintain my own projects. Any time that is spent maintaining a project that could be automated instead, is time that is being taken away from real productivity. - -**Verb does exactly what I needed** - -To that end, I wanted a documentation generator that would work in the following scenarios: - -- **simple, no config**: most projects don't need complicated docs. e.g. "just build the readme and don't ask me questions." -- **handle the boilerplate stuff**: APIs change from project to project, but my name doesn't, my github URL doesn't, and my choice of licence doesn't. Verb should know those things. -- **don't ask me questions**: I just want to run `verb`, and it should work. No setup or config. There is more than enough data in package.json to handle the boilerplate part of a readme. -- **generate API docs**: When I want [API docs](#api-docs), I should have to jump through hoops, or add `.json` files to directories. I should be able to add the docs wherever I want, to the README, separate docs, or use templates to generate a gh-pages site. -- **render markdown, not HTML**: this one was important to me. There are hundreds of [great libs](https://github.com/jonschlinkert/remarkable) that can render markdown to HTML. Once you have well-formatted markdown documentation, it's easy to convert to HTML. diff --git a/docs/sections/verb-md.md b/docs/sections/verb-md.md deleted file mode 100644 index a9b20fc9..00000000 --- a/docs/sections/verb-md.md +++ /dev/null @@ -1,33 +0,0 @@ - -Add a `.verb.md.` [template]() to your project and run `verb` from the command line to generate the project's readme using data from package.json. - -If you need more, use a [verbfile.js](). - -**Example .verb.md** - -This is a basic readme template that Verb's maintainers like to use. - -```markdown -# {%%= name %} {%%= badge("fury") %} - -> {%%= description %} - -## Install -{%%= include("install") %} - -## API -{%%= apidocs("index.js") %} - -## Author -{%%= include("author") %} - -## License -{%%= copyright() %} -{%%= license() %} - -*** - -{%%= include("footer") %} -``` - -`.verb.md` files are rendered using data from `package.json`, but Verb is not restricted to package.json. You can use any data you want, with any templates, helpers, etc. diff --git a/docs/sections/verbfile.md b/docs/sections/verbfile.md deleted file mode 100644 index 8a1d38f7..00000000 --- a/docs/sections/verbfile.md +++ /dev/null @@ -1,21 +0,0 @@ - -For projects that need more than readme documentation, you can add a `verbfile.js` to the project, and be sure to install verb locally with: - -```bash -npm i verb --save-dev -``` - -**Example basic verbfile.js** - -```js -var verb = require('verb'); - -// load data for templates if needed -verb.data('foo/*.json'); - -verb.task('default', function() { - verb.src(['.verb.md', 'docs/*.md']) - .pipe(verb.dest('./')); -}); -``` - diff --git a/docs/sections/why.md b/docs/sections/why.md deleted file mode 100644 index 1b1051be..00000000 --- a/docs/sections/why.md +++ /dev/null @@ -1 +0,0 @@ -It's magical and smells like chocolate. If that's not enough for you, it's also the most powerful and easy-to-use documentation generator for node.js. And it's magical. diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..207b3d0f --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,34 @@ +'use strict'; + +const gulp = require('gulp'); +const mocha = require('gulp-mocha'); +const istanbul = require('gulp-istanbul'); +const eslint = require('gulp-eslint'); +const unused = require('gulp-unused'); + +const lib = ['index.js', 'lib/**/*.js', 'bin/*.js']; + +gulp.task('coverage', function() { + return gulp.src(lib) + .pipe(istanbul()) + .pipe(istanbul.hookRequire()); +}); + +gulp.task('test', ['coverage'], function() { + return gulp.src('test/*.js') + .pipe(mocha({reporter: 'spec'})) + .pipe(istanbul.writeReports()); +}); + +gulp.task('lint', function() { + return gulp.src(['*.js', 'test/*.js'].concat(lib)) + .pipe(eslint()) + .pipe(eslint.format()); +}); + +gulp.task('unused', function() { + return gulp.src(['index.js', 'lib/**/*.js', 'bin/*.js']) + .pipe(unused({keys: Object.keys(require('./lib/utils.js'))})); +}); + +gulp.task('default', ['test', 'lint']); diff --git a/index.js b/index.js index 248297b9..91d5cdcc 100644 --- a/index.js +++ b/index.js @@ -1,143 +1,3 @@ 'use strict'; -var diff = require('diff'); -var chalk = require('chalk'); -var extend = require('extend-shallow'); -var Template = require('template'); -var Composer = require('composer').Composer; -var vfs = require('vinyl-fs'); - -var stack = require('./lib/stack'); -var init = require('./lib/init'); - -/** - * Initialize `Verb` - * - * @param {Object} `context` - * @api private - */ - -function Verb() { - Template.apply(this, arguments); - Composer.apply(this, arguments); - init(this); -} - -extend(Verb.prototype, Composer.prototype); -Template.extend(Verb.prototype); - -/** - * Set application defaults that may be overridden by the user. - * This is a temporary method and should not be used. - * - * @param {String} `key` - * @param {*} `value` - * @api private - */ - -Verb.prototype.defaults = function(key/*, value*/) { - if (typeof key === 'object') { - arguments[0] = {defaults: arguments[0]}; - } else { - arguments[0] = 'defaults.' + arguments[0]; - } - this.option.apply(this, arguments); - return this; -}; - -/** - * Glob patterns or filepaths to source files. - * - * ```js - * verb.src('src/*.hbs', {layout: 'default'}) - * ``` - * - * @param {String|Array} `glob` Glob patterns or file paths to source files. - * @param {Object} `options` Options or locals to merge into the context and/or pass to `src` plugins - * @api public - */ - -Verb.prototype.src = function(glob, opts) { - return stack.src(this, glob, opts); -}; - -/** - * Specify a destination for processed files. - * - * ```js - * verb.dest('dist') - * ``` - * - * @param {String|Function} `dest` File path or rename function. - * @param {Object} `options` Options and locals to pass to `dest` plugins - * @api public - */ - -Verb.prototype.dest = function(dest, opts) { - return stack.dest(this, dest, opts); -}; - -/** - * Copy a `glob` of files to the specified `dest`. - * - * ```js - * verb.task('assets', function() { - * verb.copy('assets/**', 'dist'); - * }); - * ``` - * - * @param {String|Array} `glob` - * @param {String|Function} `dest` - * @return {Stream} Stream, to continue processing if necessary. - * @api public - */ - -Verb.prototype.copy = function(glob, dest, opts) { - return vfs.src(glob, opts).pipe(vfs.dest(dest, opts)); -}; - -/** - * Display a visual representation of the difference between - * two objects or strings. - * - * ```js - * var doc = verb.views.docs['foo.md']; - * verb.render(doc, function(err, content) { - * verb.diff(doc.orig, content); - * }); - * ``` - * - * @param {Object|String} `a` - * @param {Object|String} `b` - * @param {String} `methodName` Optionally pass a [jsdiff] method name to use. The default is `diffJson` - * @api public - */ - -Verb.prototype.diff = function(a, b, method) { - method = method || 'diffJson'; - a = a || this.env; - b = b || this.cache.data; - diff[method](a, b).forEach(function (res) { - var color = chalk.gray; - if (res.added) { - color = chalk.green; - } - if (res.removed) { - color = chalk.red; - } - process.stderr.write(color(res.value)); - }); - console.log('\n'); -}; - -/** - * Expose `verb.Verb` - */ - -Verb.prototype.Verb = Verb; - -/** - * Expose `verb` - */ - -module.exports = new Verb(); +module.exports = require('./lib/Verb'); diff --git a/lib/Verb.js b/lib/Verb.js new file mode 100644 index 00000000..cf1b5892 --- /dev/null +++ b/lib/Verb.js @@ -0,0 +1,128 @@ +/**! + * verb + * Copyright (c) 2016, Jon Schlinkert. + * Licensed under the MIT License. + */ + +'use strict'; + +const Generate = require('generate'); +const utils = require('./utils'); +const pkg = require('../package'); + +/** + * Create a verb application with `options`. + * + * ```js + * var verb = require('verb'); + * var app = verb(); + * ``` + * @param {Object} `options` Settings to initialize with. + * @api public + */ + +class Verb extends Generate { + constructor(options) { + super(options); + this.is('verb'); + this.initVerb(this.options); + } + + /** + * Initialize verb data + */ + + initVerb(opts) { + Verb.emit('verb.preInit', this, this.base); + + /** + * Data + */ + + this.data('before', {}); + this.data('after', {}); + this.data('runner', { + name: 'verb', + version: pkg.version, + homepage: pkg.homepage + }); + + /** + * Options + */ + + this.option('lookup', Verb.lookup(this)); + this.option('toAlias', Verb.toAlias); + this.option('help', { + command: 'verb', + configname: 'verbfile', + appname: 'verb' + }); + + /** + * Listeners + */ + + this.on('option', (key, val) => { + if (key === 'dest') { + this.cwd = val; + } + }); + + this.on('ask', (answerVal, answerKey, question) => { + if (typeof answerVal === 'undefined') { + const segs = answerKey.split('author.'); + if (segs.length > 1) { + this.questions.answers[answerKey] = this.common.get(segs.pop()); + } + } + }); + + /** + * Middleware + */ + + this.preWrite(/(^|\/)[$_]/, (file, next) => { + file.basename = file.basename.replace(/^_/, '.'); + file.basename = file.basename.replace(/^\$/, ''); + next(); + }); + + this.constructor.emit('verb.postInit', this, this.base); + } +} + +/** + * Expose custom lookup function for resolving generators + */ + +Verb.lookup = app => key => { + const patterns = [key]; + if (!/^verb-generate-([^-]+)/.test(key)) { + patterns.unshift(`verb-generate-${key}`); + } + if (app.enabled('generate')) { + patterns.push(`generate-${key}`); + } + return patterns; +}; + +/** + * Convert the given `name` to the `alias` to be used in the + * command line. + */ + +Verb.toAlias = name => name.replace(/^(?:verb-generate-([^-]+)$)|(?:generate-)/, '$1'); + +/** + * Expose `pkg` as a static property + */ + +Verb.pkg = pkg; + +/** + * Expose `Verb` + */ + +utils.stores(Verb.prototype); +module.exports = Verb; diff --git a/lib/commands.js b/lib/commands.js new file mode 100644 index 00000000..1f949b33 --- /dev/null +++ b/lib/commands.js @@ -0,0 +1,11 @@ +'use strict'; + +const commands = require('./commands/'); + +module.exports = (app, options) => { + for (const key in commands) { + if (commands.hasOwnProperty(key)) { + app.cli.map(key, commands[key](app, options)); + } + } +}; diff --git a/lib/commands/defaults.js b/lib/commands/defaults.js new file mode 100644 index 00000000..514ded3f --- /dev/null +++ b/lib/commands/defaults.js @@ -0,0 +1,95 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const merge = require('mixin-deep'); +const utils = require('../utils'); + +/** + * Persist a value to a namespaced defaults object in package.json. For + * example, if you're using `verb`, the value would be saved to the + * `verb` object. + * + * ```sh + * # display the defaults + * $ verb --defaults + * # set a boolean for the current project + * $ verb --defaults=toc + * # save the cwd to use for the current project + * $ verb --defaults=cwd:foo + * # save the tasks to run for the current project + * $ verb --defaults=tasks:readme + * ``` + * + * @name defaults + * @param {Object} verb + * @api public + * @cli public + */ + +module.exports = (verb, base, options) => { + let ran = false; + + return (val, key, config, next) => { + if (ran === true) { + next(); + return; + } + + ran = true; + + // Get the keys of properties defined by an `--init` prompt + const keys = verb.get('cache.initKeys') || []; + const name = verb._name.toLowerCase(); + + if (utils.show(val)) { + const pkgConfig = {}; + pkgConfig[name] = verb.pkg.get(name) || {}; + console.error(utils.formatValue(pkgConfig)); + next(); + return; + } + + const pkgPath = path.resolve(verb.cwd, 'package.json'); + let pkg = {}; + + if (utils.exists(pkgPath)) { + pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + } + + pkg = merge({}, verb.pkg.data, pkg); + verb.pkg.del(name); + let orig = pkg[name] || {}; + + // Normalize both the old and new values before merging, using + // A schema that is specifically used for normalizing values to + // Be written back to package.json + const tmp = verb.cli.schema.normalize({ config: {} }) || {}; + orig = tmp.config; + + // Merge the normalized values + let merged = val; + if (utils.isObject(val) && utils.isObject(orig)) { + merged = merge({}, orig, val); + } + + // Show the new value in the console + const show = utils.pick(merged, keys); + verb.pkg.logInfo('updated package.json config with', show); + + // Update options and `cache.config` + verb.set('cache.config', merged); + verb.emit('config', merged); + + // Update the config property + config[key] = merged; + if (verb.pkg.queued === true) { + verb.pkg.data = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + } + + // Re-set updated config object in `package.json` + verb.pkg.set(name, merged); + verb.pkg.save(); + verb.cli.process(merged, next); + }; +}; diff --git a/lib/commands/dest.js b/lib/commands/dest.js new file mode 100644 index 00000000..212cd779 --- /dev/null +++ b/lib/commands/dest.js @@ -0,0 +1,12 @@ +'use strict'; + +const path = require('path'); + +module.exports = (app, base) => (val, key, config, next) => { + if (typeof val === 'undefined') { + config[key] = app.cwd; + } else { + config[key] = path.resolve(val); + } + next(); +}; diff --git a/lib/middleware/index.js b/lib/commands/index.js similarity index 100% rename from lib/middleware/index.js rename to lib/commands/index.js diff --git a/lib/commands/init.js b/lib/commands/init.js new file mode 100644 index 00000000..a9e460b4 --- /dev/null +++ b/lib/commands/init.js @@ -0,0 +1,106 @@ +'use strict'; + +const debug = require('debug')('base:cli:init'); +const questions = require('../questions'); +const utils = require('../utils'); + +const filterDeps = (app, names) => { + if (!names) return []; + + if (typeof names === 'string') { + names = names.split(','); + } + + const devDeps = app.pkg.get('devDependencies'); + const deps = app.pkg.get('dependencies'); + const res = []; + + for (const name of names) { + if (name && !devDeps?.[name] && !deps?.[name]) { + res.push(name); + } + } + + return res; +}; + +const postInit = (app, answers, cb) => { + const plugins = filterDeps(app, utils.get(answers, 'config.plugins')); + + // eslint-disable-next-line no-negated-condition + if (!plugins.length) { + cb(null, answers); + + } else { + app.ask('after', { save: false }, (err, res) => { + if (err) return cb(err); + + const answer = utils.get(res, 'after.plugins'); + if (answer === true) { + app.pkg.save(); + app.npm.saveDev(plugins, err => { + if (err) return cb(err); + app.pkg.queued = true; + cb(null, answers); + }); + } else { + cb(null, answers); + } + }); + } +}; + +const ask = (app, options, cb) => { + if (typeof app.questions === 'undefined') { + cb(new Error('expected base-questions plugin to be defined')); + return; + } + + if (typeof options === 'function') { + cb = options; + options = {}; + } + + options = utils.extend({}, options, app.options); + questions(app, options); + + app.ask('init.choose', { save: false }, function(err, answers) { + if (err) return cb(err); + debug('finished with init.choose "%j"', answers); + postInit(app, answers, cb); + }); +}; + +const prompt = (app, next) => { + ask(app, { save: false }, (err, answers) => { + if (err) { + next(err); + return; + } + const config = answers && answers.config || {}; + app.set('cache.initKeys', Object.keys(config)); + app.cli.process(answers, next); + }); +}; + +/** + * Initialize a prompt session and persist the answers to the `verb` object + * in the package.json in the current working directory. + * + * ```sh + * $ --init + * ``` + * @name --init + * @api public + */ + +module.exports = (app, base, options) => (val, key, config, next) => { + prompt(app, next); +}; + +/** + * Expose methods + */ + +module.exports.postInit = postInit; +module.exports.prompt = prompt; diff --git a/lib/commands/version.js b/lib/commands/version.js new file mode 100644 index 00000000..a490a018 --- /dev/null +++ b/lib/commands/version.js @@ -0,0 +1,8 @@ +'use strict'; + +const pkg = require('../../package'); + +module.exports = app => (val, key, config, next) => { + console.log(app.log.cyan(`${app._name} v${pkg.version}`)); + process.exit(); +}; diff --git a/lib/diff.js b/lib/diff.js new file mode 100644 index 00000000..dbc5be34 --- /dev/null +++ b/lib/diff.js @@ -0,0 +1,51 @@ +'use strict'; + +const differ = require('diff'); +const through = require('through2'); +const utils = require('./utils'); + +const color = stat => { + if (stat.removed) return 'red'; + if (stat.added) return 'green'; + return 'gray'; +}; + +const diff = (a, b, method) => { + differ[method || 'diffWords'](a, b).forEach(stat => { + process.stderr.write(utils.log[color(stat)](stat.value)); + }); + console.error(); +}; + +/** + * Output to the console a visual representation of the difference between + * two objects or strings. + * + * @param {Object|String} `a` + * @param {Object|String} `b` + * @api public + */ + +module.exports = options => { + const cache = {}; + let prev; + + return (a, b) => through.obj((file, enc, next) => { + if (options?.diff === false) { + next(null, file); + return; + } + + const contents = file.contents.toString(); + cache[a] = contents; + const str = b ? cache[b] || b : prev; + + if (typeof str !== 'undefined') { + diff(contents, cache[b]); + next(); + return; + } + prev = contents; + next(null, file); + }); +}; diff --git a/lib/format.js b/lib/format.js new file mode 100644 index 00000000..53ce0cb4 --- /dev/null +++ b/lib/format.js @@ -0,0 +1,33 @@ +'use strict'; + +const path = require('path'); +const reflinks = require('./reflinks'); +const utils = require('./utils'); + +/** + * Format a markdown file using [pretty-remarkable][]. Optionally + * specify a `--dest` directory and/or file `--name`. + * + * ```sh + * $ verb --format foo/bar.md + * ``` + * @name --format + * @api public + */ + +module.exports = (verb, options) => (filepath, key, config, next) => { + if (!filepath || typeof filepath !== 'string') { + next(); + return; + } + + verb.src(path.resolve(filepath)) + .pipe(reflinks(verb)) + .pipe(utils.format()) + .pipe(verb.dest(file => { + file.basename = config.name || path.basename(filepath); + return config.dest || path.resolve(path.dirname(filepath)); + })) + .on('error', next) + .on('end', next); +}; diff --git a/lib/generator.js b/lib/generator.js new file mode 100644 index 00000000..93abddcb --- /dev/null +++ b/lib/generator.js @@ -0,0 +1,464 @@ +'use strict'; + +const path = require('path'); +const cwd = path.resolve.bind(path, __dirname, 'templates'); +const commands = require('./commands/'); +const utils = require('./utils'); +const list = require('./list'); +const argv = utils.parseArgs(process.argv.slice(2)); +const diff = require('./diff')(argv); + +/** + * Generate a file + */ + +const file = (verb, src, options, cb) => { + const defaults = { cwd: cwd(), dest: verb.cwd }; + const opts = utils.extend({}, defaults, options); + const dest = path.resolve(opts.dest); + + verb.engine('*', require('engine-base')); + verb + .src(src, { cwd: opts.cwd, layout: null }) + .pipe(verb.renderFile('*')) + .pipe(utils.format()) + .pipe(verb.conflicts(dest)) + .pipe( + verb.dest(file => { + if (opts.name) file.basename = opts.name; + file.basename = file.basename.replace(/^_/, '.'); + file.basename = file.basename.replace(/^\$/, ''); + verb.log.success('created', file.relative); + return dest; + }) + ) + .on('end', cb); +}; + +/** + * Built-in verb tasks + */ + +module.exports = (verb, base) => { + const common = new utils.DataStore('common-config'); + const gm = path.resolve.bind(path, utils.gm); + const cwd = path.resolve.bind(path, verb.cwd); + + verb.use(utils.middleware()); + + /** + * Listen for errors + */ + + verb.on('error', err => { + console.error(err); + process.exit(1); + }); + + /** + * Format a markdown file using [pretty-remarkable][]. Optionally + * specify a `--dest` directory and/or file `--name`. + * + * ```sh + * $ verb format --src=foo/bar.md --dest=baz + * ``` + * @name format + * @api public + */ + + verb.task('format', cb => { + const src = verb.option('src'); + const dest = verb.option('dest'); + + verb + .src(src) + .pipe(utils.reflinks(verb)) + .pipe(diff('before')) + .pipe(utils.format()) + .pipe(diff('after', 'before')) + .pipe( + verb.dest(file => { + if (argv.name) file.basename = argv.name; + return dest || file.dirname; + }) + ) + .on('data', file => { + console.log('formatted "%s"', file.relative); + }) + .on('error', cb) + .on('end', cb); + }); + + /** + * Render a single `--src` file to the given `--dest` or current working directory. + * + * ```sh + * $ verb defaults:render + * # aliased as + * $ verb render + * ``` + * @name render + * @api public + */ + + verb.task('render', cb => { + if (!verb.option('src')) { + verb.emit('error', new Error('Expected a `--src` filepath')); + } else if (!verb.option('dest')) { + verb.build(['dest', 'render'], cb); + } else if (verb.option('dest') && verb.option('src')) { + file(verb, verb.option('src'), { dest: verb.cwd }, cb); + } + }); + + /** + * The `new` sub-generator has a handful of tasks for quickly generating a file from + * a template. Tasks on the sub-generator are called with `verb new:foo`, where `foo` + * is the name of the task to run. + * + * @name new + * @api public + */ + + verb.register('new', app => { + app.option(verb.options); + + /** + * On all generators, the `default` task is executed when no other task name + * is given. Thus, on the `new` sub-generator, the `new:default` task is an alias that allows + * you to execute the `new:verbfile` task with the following command: + * + * ```sh + * $ verb new + * # or, if you prefer verbose commands + * $ verb new:default + * ``` + * @name new:default + * @api public + */ + + app.task('default', ['verbfile']); + + /** + * Generate a `verbfile.js` in the current working directory. + * + * ```sh + * $ verb new:verbfile + * ``` + * @name new:verbfile + * @api public + */ + + app.task('verbfile', cb => { + file(app, 'verbfile.js', null, cb); + }); + + /** + * Generate a `.verb.md` file in the current working directory. + * + * ```sh + * $ verb new:verbmd + * ``` + * @name new:verbmd + * @api public + */ + + app.task('verbmd', cb => { + file(app, '_verb.md', null, cb); + }); + + /** + * Generate a `.verbrc.json` file in the current working directory. + * + * ```sh + * $ verb new:verbmd + * ``` + * @name new:verbmd + * @api public + */ + + app.task('rc', cb => { + file(app, '_verbrc.json', null, cb); + }); + + /** + * Generate a `README.md` in the current working directory (the task will prompt + * for project `name` and `description`). + * + * ```sh + * $ verb new:readme + * ``` + * @name new:readme + * @api public + */ + + app.task('readme', cb => { + file(app, 'README.md', null, cb); + }); + + /** + * Add a `verb` config object to `package.json` in the current working directory. + * + * ```sh + * $ verb new:package-config + * ``` + * @name new:package-config + * @api public + */ + + app.task('package-config', cb => { + const config = app.pkg.get('verb'); + + if (config && app.options.force !== true) { + console.log('verb config already exists in package.json'); + cb(); + return; + } + + app.pkg.set('verb', { + toc: false, + layout: 'default', + tasks: ['readme'], + plugins: ['gulp-format-md'], + reflinks: ['verb'], + lint: { + reflinks: true + } + }); + + app.pkg.save(); + cb(); + }); + + /** + * Prompts the user to add a `.verb.md` (this task runs automatically when the + * `verb` command is given if `verbfile.js` and `.verb.md` are both missing from the + * current working directory): + * + * ```sh + * $ verb new:prompt-verbmd + * ``` + * @name new:prompt-verbmd + * @api public + */ + + app.task('prompt-verbmd', { silent: true }, async cb => { + app.confirm('verbmd', 'Looks like .verb.md is missing, want to add one?'); + + app.ask('verbmd', { save: false }, (err, answers) => { + if (err) { + cb(err); + return; + } + + if (answers.verbmd) { + const config = app.options.pkg ? 'package-config' : 'prompt-package-config'; + app.build(['verbmd', config], cb); + } else { + cb(); + } + }); + }); + + app.task('prompt-package-config', { silent: true }, cb => { + if (app.pkg.get('verb')) { + cb(); + return; + } + + app.confirm('package-config', 'Want to add a verb config to package.json?'); + app.ask('package-config', { save: false }, (err, answers) => { + if (err) { + cb(err); + return; + } + + if (answers['package-config']) { + app.build('package-config', cb); + } else { + cb(); + } + }); + }); + }); + + /** + * Display a list of installed verb generators. + * + * ```sh + * $ verb list + * ``` + * @name list + * @api public + */ + + verb.task('list', { silent: true }, () => verb + .src([gm('verb-generate-*'), cwd('node_modules/verb-generate-*')]) + .pipe( + utils.through.obj((file, enc, next) => { + file.alias = verb.toAlias(file.basename); + next(null, file); + }) + ) + .pipe(list(verb))); + + /** + * Display a help menu of available commands and flags. + * + * ```sh + * $ verb help + * ``` + * @name help + * @api public + */ + + verb.task('init', { silent: true }, cb => { + verb.question('init', 'Would you like to use defaults, or choose settings?', { + type: 'list', + choices: ['defaults', 'choose'], + all: false + }); + + verb.ask('init', { save: false }, (err, answers) => { + if (err) { + cb(err); + return; + } + + const defaults = verb.globals.get('defaults'); + + switch (answers.init) { + case 'defaults': { + commands.init.postInit(verb, { config: defaults }, cb); + return; + } + + case 'choose': + default: { + commands.init.prompt(verb, cb); + + } + } + }); + }); + + /** + * Save personal defaults in user home. + */ + + verb.register('store', app => { + app.enable('silent'); + + app.task('defaults', cb => { + const defaults = app.globals.has('defaults.defined'); + const message = defaults + ? 'Would you like to update defaults now?' + : 'No defaults found, would you like to set them now?'; + + app.confirm('defaults', message); + app.ask('defaults', (err, answers) => { + if (err) return cb(err); + }); + }); + + app.task('del', cb => { + const keys = ['name', 'username', 'twitter', 'email']; + keys.forEach(key => { + console.log(verb.log.red(' Deleted:'), key, common.get(key)); + common.del(keys); + }); + cb(); + }); + + app.task('show', cb => { + const keys = ['name', 'username', 'twitter', 'email']; + console.log(); + keys.forEach(key => { + console.log(key + ': ' + verb.log.cyan(common.get(key))); + }); + console.log(); + cb(); + }); + + app.task('me', cb => { + console.log(); + console.log( + ' Answers to the following questions will be stored in:', + verb.log.bold('~/.common-config.json') + ); + console.log( + ' The stored values will be used later in (your) templates.' + ); + console.log(` To skip a question, just hit ${verb.log.bold('')}`); + console.log(); + + app.question('common.name', 'What is your name?'); + app.question('common.username', 'GitHub username?'); + app.question('common.url', 'GitHub URL?'); + app.question('common.twitter', 'Twitter username?'); + app.question('common.email', 'Email address?'); + + app.ask('common', { save: false }, (err, answers) => { + if (err) return cb(err); + + if (!answers.common) { + cb(); + return; + } + + const vals = []; + for (const key in answers.common) { + if (answers.common.hasOwnProperty(key)) { + const val = answers.common[key]; + common.set(key, val); + vals.push(verb.log.green(key + ': ' + val)); + } + } + + console.log(); + console.log(' Saved:'); + console.log(); + console.log(' ', vals.join('\n ')); + console.log(); + console.log(' To delete these values, run:'); + console.log(); + console.log(verb.log.bold(' $ verb store:del')); + console.log(); + console.log(' To update these values, run:'); + console.log(); + console.log(verb.log.bold(' $ verb store:me')); + console.log(); + cb(); + }); + }); + + app.task('default', ['me']); + }); + + /** + * Display a help menu of available commands and flags. + * + * ```sh + * $ verb help + * ``` + * @name help + * @api public + */ + + verb.task('help', { silent: true }, cb => { + verb.enable('silent'); + verb.cli.process({ help: true }, cb); + }); + + /** + * Default task for the built-in `defaults` generator. + * + * ```sh + * $ verb defaults + * ``` + * @name defaults + * @api public + */ + + verb.task('default', ['help']); +}; diff --git a/lib/helpers/apidocs.js b/lib/helpers/apidocs.js deleted file mode 100644 index 7db075e2..00000000 --- a/lib/helpers/apidocs.js +++ /dev/null @@ -1,20 +0,0 @@ - -var comments = require('js-comments'); - -module.exports = function(app) { - app.loader('apidocs', function(templates) { - for (var key in templates) { - if (templates.hasOwnProperty(key)) { - var file = templates[key]; - var obj = comments.parse(file.content); - file.content = comments.render(obj); - } - } - return templates; - }); - - return function (patterns, opts) { - var view = app.lookup('comments', patterns); - return view.content; - } -}; diff --git a/lib/helpers/async.js b/lib/helpers/async.js deleted file mode 100644 index ebfc6212..00000000 --- a/lib/helpers/async.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var helper = require('async-helper-base'); -var example = require('./custom/example'); -var reflinks = require('helper-reflinks'); -var related = require('helper-related'); - -/** - * Transform for loading default async helpers - */ - -module.exports = function(app) { - app.asyncHelper('reflinks', reflinks); - app.asyncHelper('related', related()); - app.asyncHelper('example', helper('example', example)); - app.asyncHelper('include', helper('include')); - app.asyncHelper('badge', helper('badge')); - app.asyncHelper('docs', helper('doc')); -}; diff --git a/lib/helpers/collections.js b/lib/helpers/collections.js deleted file mode 100644 index 602bb76a..00000000 --- a/lib/helpers/collections.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -var _ = require('lodash'); - -/** - * Transform for loading helper collections - * - * - Loads template-helpers - * - Loads logging-helpers - * - Exposes markdown-utils as helpers - * - exposes path helpers on the `path.` property - * - * ```js - * {%= mdu.link(author.name, author.url) %} - * //=> [Jon Schlinkert](https://github.com/jonschlinkert) - * - * {%= path.extname("foo.md") %} - * //=> '.md' - * ``` - */ - -module.exports = function(app) { - app.helpers({console: console}); - app.helpers(require('logging-helpers')); - - // namespaced helpers - app.helpers({fn: require('./custom/')}); - app.helpers({mdu: require('markdown-utils')}); - app.helpers({mm: require('micromatch')}); - - // remove `path` helpers from root and add them to `path.` - var helpers = require('template-helpers'); - app.helpers(_.omit(helpers._, Object.keys(helpers.path))); - app.helpers({path: helpers.path}); -}; diff --git a/lib/helpers/custom/example.js b/lib/helpers/custom/example.js deleted file mode 100644 index 16018b6e..00000000 --- a/lib/helpers/custom/example.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -/** - * Create a code example from the contents of the specified - * JavaScript file. - * - * ```js - * {%%= example("foo", {name: "my-module"}) %} - * ``` - * - * @param {String} `fp` The path of the file to include. - * @param {String} `options` - * @option {String} `name` Replace `./` in `require('./')` with the given name. - * @return {String} - */ - -module.exports = function example(str, opts) { - if (typeof str !== 'string') { - throw new TypeError('example-helper expects a string.'); - } - - opts = opts || {}; - if (opts.name) { - str = str.split(/\(['"]\.\/['"]\)/).join('(\'' + opts.name + '\')'); - } - - return str; -}; diff --git a/lib/helpers/custom/github.js b/lib/helpers/custom/github.js deleted file mode 100644 index d0cd78a2..00000000 --- a/lib/helpers/custom/github.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('../../utils').linkify('github', true); diff --git a/lib/helpers/custom/index.js b/lib/helpers/custom/index.js deleted file mode 100644 index 874ea554..00000000 --- a/lib/helpers/custom/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/lib/helpers/custom/linkify.js b/lib/helpers/custom/linkify.js deleted file mode 100644 index 6b40b534..00000000 --- a/lib/helpers/custom/linkify.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('../../utils').linkify; diff --git a/lib/helpers/custom/resolve.js b/lib/helpers/custom/resolve.js deleted file mode 100644 index 5b102fab..00000000 --- a/lib/helpers/custom/resolve.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -var resolve = require('helper-resolve'); - -/** - * Return a property from the package.json of the specified module. - * _(The module must be installed in `node_modules`)_. - * - * ```js - * {%%= resolve("micromatch") %} - * //=> 'node_modules/micromatch/index.js' - * - * {%%= resolve("micromatch", "version") %} - * //=> '2.2.0' - * ``` - * - * @param {String} `fp` The path of the file to include. - * @param {String} `options` - * @option {String} `name` Replace `./` in `require('./')` with the given name. - * @return {String} - */ - -module.exports = function resolve_(name, key) { - return resolve.sync(name)[typeof key === 'string' ? key : 'main']; -}; diff --git a/lib/helpers/custom/shield.js b/lib/helpers/custom/shield.js deleted file mode 100644 index 3faa64e0..00000000 --- a/lib/helpers/custom/shield.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var _ = require('lodash'); - -/** - * Transform for loading the `{%= shield() %}` helper - * These aren't really implemented yet. this is a - * placeholder for better logic - */ - -module.exports = function (verb) { - verb.badge('npm', '[![{%= name %}](http://img.shields.io/npm/v/{%= name %}.svg)](https://www.npmjs.org/package/{%= name %})'); - - verb.badge('npm2', '[![{%= name %}](http://img.shields.io/npm/dm/{%= name %}.svg)](https://www.npmjs.org/package/{%= name %})'); - - verb.badge('nodei', '[![{%= name %}](https://nodei.co/npm/{%= name %}.png?downloads=true "{%= name %}")](https://nodei.co/npm/{%= name %})'); - - verb.badge('travis', '[![Build Status](http://img.shields.io/travis/{%= name %}.svg)](https://travis-ci.org/{%= name %})'); - - return function shield(name, locals) { - var ctx = _.extend({}, this.context, locals); - return this.app.render(verb.views.badges[name], ctx); - }; -}; diff --git a/lib/helpers/custom/twitter.js b/lib/helpers/custom/twitter.js deleted file mode 100644 index b7d9f41c..00000000 --- a/lib/helpers/custom/twitter.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('../../utils').linkify('twitter'); diff --git a/lib/helpers/sync.js b/lib/helpers/sync.js deleted file mode 100644 index 6fa5d7f3..00000000 --- a/lib/helpers/sync.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -/** - * Transform for loading default sync helpers - */ - -module.exports = function(app) { - app.helper('date', require('helper-date')); - app.helper('apidocs', require('template-helper-apidocs')); - app.helper('multiToc', require('helper-toc')(app.option('toc'))); - app.helper('codelinks', require('helper-codelinks')(app)); - app.helper('changelog', require('helper-changelog')(app)); - app.helper('copyright', require('helper-copyright')); - app.helper('coverage', require('helper-coverage')); - app.helper('license', require('helper-license')); - app.helper('relative', require('relative')); - app.helper('year', require('year')); -}; diff --git a/lib/init.js b/lib/init.js deleted file mode 100644 index 9cd013d3..00000000 --- a/lib/init.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -var init = require('./transforms/init'); -var config = require('./transforms/config'); -var mod = require('./transforms/modifiers'); -var env = require('./transforms/env'); - -/** - * Load initialization transforms - * - * | config - * | loaders - * | templates - * | options - * | middleware - * | plugins - * | load - * | engines - * | helpers - load helpers last - */ - -module.exports = function(app) { - app.transform('metadata', init.metadata); - app.transform('ignore', init.ignore); - app.transform('files', env.files); - - app.transform('env', env.env); - app.transform('pkg', env.pkg); - app.transform('keys', env.keys); - app.transform('paths', env.paths); - app.transform('cwd', env.cwd); - app.transform('repo', mod.repository); - app.transform('author', env.author); - app.transform('user', env.user); - app.transform('username', env.username); - app.transform('github', env.github); - app.transform('travis', env.travis); - app.transform('fork', env.fork); - app.transform('missing', env.missing); - - app.transform('github-url', mod.github_url); - app.transform('twitter-url', mod.twitter_url); - - app.once('loaded', function () { - app.transform('defaults', init.defaults); - app.transform('runner', init.runner); - app.transform('argv', init.argv); - app.transform('config', config); - app.transform('loaders', init.loaders); - app.transform('create', init.templates); - app.transform('engines', init.engines); - app.transform('middleware', init.middleware); - app.transform('helpers', init.helpers); - app.transform('load', init.load); - app.transform('plugins', init.plugins); - app.emit('init'); - }); - - app.once('init', function () { - app.transform('helpers', init.helpers); - app.emit('finished'); - }); - - app.once('finished', function () { - app.transform('checkup', init.checkup); - }); -}; diff --git a/lib/list.js b/lib/list.js new file mode 100644 index 00000000..60204f9f --- /dev/null +++ b/lib/list.js @@ -0,0 +1,38 @@ +'use strict'; + +const path = require('path'); +const utils = require('./utils'); + +module.exports = function(app) { + function bold(str) { + return app.log.underline(app.log.bold(str)); + } + + const list = [[bold('version'), bold('name'), bold('alias')]]; + const cache = {}; + return utils.through.obj(function(file, enc, next) { + if (cache[file.stem]) { + next(); + return; + } + + cache[file.stem] = true; + const pkgPath = path.resolve(file.path, 'package.json'); + const pkg = require(pkgPath); + list.push([app.log.gray(pkg.version), file.basename, app.log.cyan(file.alias)]); + next(); + }, function(cb) { + console.log(); + console.log(utils.table(list, { + stringLength(str) { + return utils.strip(str).length; + } + })); + + console.log(); + console.log(app.log.magenta(list.length + ' verb generators installed')); + console.log(); + cb(); + }); +}; + diff --git a/lib/middleware/append.js b/lib/middleware/append.js deleted file mode 100644 index 35485316..00000000 --- a/lib/middleware/append.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -/** - * `append` post-render middleware. - * - * If a string is defined on `verb.cache.data.append`, - * it will be appended to the content of every file - * that matches the route. - */ - -module.exports = function(app) { - return function (file, next) { - file.content += app.get('data.append') || ''; - next(); - }; -}; diff --git a/lib/middleware/assets.js b/lib/middleware/assets.js deleted file mode 100644 index 27abde0c..00000000 --- a/lib/middleware/assets.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Module dependencies - */ - -var path = require('path'); -var compute = require('computed-property'); -var relative = require('relative-dest'); -var extend = require('extend-shallow'); - -/** - * Calculate the path from the `assets` or `public` - * path define on the options to the destination - * file. - */ - -module.exports = function(assemble) { - return function assetsPath(file, next) { - calculate(assemble, file, 'assets'); - calculate(assemble, file, 'public'); - next(); - }; -}; - -function calculate(assemble, file, target) { - compute(file.data, target, function () { - var opts = extend({}, assemble.options, file.options); - - // destination directory for the file - var dest = path.resolve(this.dest.dirname || process.cwd()); - target = path.resolve(dest, opts[target] || target); - - // look for `opts.assets`, fallback to `./assets` - this.assetsPath = relative(dest, target); - return this.assetsPath; - }); -} diff --git a/lib/middleware/conflict.js b/lib/middleware/conflict.js deleted file mode 100644 index 1c066634..00000000 --- a/lib/middleware/conflict.js +++ /dev/null @@ -1,158 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var chalk = require('chalk'); -var extend = require('extend-shallow'); -var symbol = require('log-symbols'); - -/** - * Resolve conflicts between helpers and data - * properties before rendering. - */ - -module.exports = function conflict_(app) { - var config = extend({}, app.options, app.get('argv')); - - if (config.verbose) { - console.log(); - console.log(chalk.bold.underline('Checking for conflicts…')); - console.log(); - } - - return function (file, next) { - if (config.nolint === true) { - return next(); - } - - var str = file.content; - var res = helpers(str); - var conflicting = problematic(app, file, res, config.verbose); - var h = {}; - - if (!conflicting.length) { - return next(); - } - - var ctx = {}; - ctx.options = _.extend({}, app.options, file.options); - ctx.context = _.merge({}, app.cache.data, file.data, file.locals); - ctx.app = app; - - file.locals = file.locals || {}; - file.locals.__ = file.locals.__ || {}; - - // console.log('actual:', app._.helpers.sync) - for (var i = 0; i < conflicting.length; i++) { - var name = conflicting[i]; - file.content = namespace(file.content, name); - - var syncFn = function () { - return app._.helpers.sync[name].apply(this, arguments); - }; - - if (syncFn) { - h[name] = _.bind(syncFn, ctx); - file.locals.__[name] = _.bind(syncFn, ctx); - app.helpers({__: h}); - delete app._.helpers.sync[name]; - } - - var asyncFn = app._.helpers.async[name]; - if (asyncFn) { - h[name] = _.bind(asyncFn, ctx); - file.locals.__[name] = _.bind(asyncFn, ctx); - app.asyncHelpers({__: h}); - delete app._.helpers.async[name]; - } - - if (!asyncFn && !syncFn) { - h[name] = noop(name); - file.locals.__[name] = noop(name); - app.helpers({__: h}); - } - } - - next(); - }; -}; - -function noop(name) { - return function () { - var msg = ''; - msg += 'ERROR! Cannot find the {%= ' + name + '() %} helper. Helpers may be '; - msg += 'registered using `app.helper()`.'; - // console.log(chalk.red(msg)); - }; -} - -function namespace(str, name) { - return str.split('{%= ' + name).join('{%= __.' + name); -} - -function helpers(str) { - var re = /\{%=\s*((?!__\.)[\w.]+)(?:\(\)|\(([^)]*?)\))\s*%}/gm; - var res = [], match; - - while (match = re.exec(str)) { - if (res.indexOf(match[1]) === -1) { - res.push(match[1].trim()); - } - } - return res; -} - -function problematic(app, file, helpers, verbose) { - var dataKeys = Object.keys(app.cache.data); - dataKeys = _.union(dataKeys, Object.keys(file.data)); - - var registered = Object.keys(app._.helpers.async); - registered = _.union(registered, Object.keys(app._.helpers.sync)); - - var h = [], d = []; - var len = helpers.length; - - while (len--) { - var helper = helpers[len]; - // if the helper name is also a data prop, it's a conflict - if (dataKeys.indexOf(helper) !== -1 && helper.indexOf('.') === -1) { - d.push(helper); - } - - // if the helper is not registered, it's a conflict - if (registered.indexOf(helper) === -1 && helper.indexOf('.') === -1) { - h.push(helper); - } - } - - message(h, d, verbose, file); - return _.union(h, d); -} - - -function message(h, d, verbose, file) { - if (!verbose) return; - var fp = file.path.split(/[\\\/]/).slice(-2).join('/'); - - var hlen = h.length; - var dlen = d.length; - - if (hlen > 0) { - console.log(symbol.error, '', chalk.red(hlen + ' missing helper' + s(hlen) + ' found in', fp)); - h.forEach(function (ele) { - console.log(' -', ele); - }); - console.log(); - } - - if (dlen > 0) { - console.log(symbol.warning, '', chalk.yellow(dlen + ' data/helper conflict' + s(dlen) + ' found in', fp)); - d.forEach(function (ele) { - console.log(' -', ele); - }); - console.log(); - } -} - -function s(len) { - return len > 1 ? 's' : ''; -} diff --git a/lib/middleware/copyright.js b/lib/middleware/copyright.js deleted file mode 100644 index 80a3cab4..00000000 --- a/lib/middleware/copyright.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var update = require('update-copyright'); -var parse = require('parse-copyright'); - -/** - * Add copyright information to the context. - * - * ```js - * // get - * console.log(verb.get('data.copyright')); - * // or directly - * console.log(verb.cache.data.copyright); - * // used by the copyright helper - * {%= copyright() %} - * ``` - */ - -module.exports = function copyright_(verb) { - var copyright = verb.get('data.copyright'); - var readme = verb.files('README.md'); - var parsed = false; - - if (!parsed && !copyright && readme.length) { - var str = fs.readFileSync(readme[0], 'utf8'); - var res = update(str); - if (res) { - parsed = true; - verb.set('data.copyright.statement', res); - } - } - - return function (file, next) { - if (typeof copyright === 'string') return next(); - if (typeof copyright === 'object' && Object.keys(copyright).length) { - return next(); - } - copyright = verb.get('data.copyright') || parse(file.content)[0] || {}; - verb.set('data.copyright', copyright); - file.data.copyright = copyright; - next(); - }; -}; diff --git a/lib/middleware/debug.js b/lib/middleware/debug.js deleted file mode 100644 index 34557cab..00000000 --- a/lib/middleware/debug.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -var get = require('get-value'); -var _ = require('lodash'); - -/** - * Convenience method for debugging. - * - * **Examples** - * - * ```js - * {%= log(debug("app")) %} - * {%= log(debug("app.cache.data")) %} - * - * {%= log(debug("file")) %} - * {%= log(debug("file.data")) %} - * - * {%= log(debug()) %} - * {%= log(debug("this")) %} - * {%= log(debug("this.dest")) %} - * ``` - * @todo move to a helper - */ - -module.exports = function (app) { - return function(file, next) { - file.data.debug = file.data.debug || {}; - - file.data.debug = function (prop) { - var segs, root, type = typeof prop; - if (type !== 'undefined') { - segs = prop.split('.'); - root = segs.shift(); - segs = segs.join('.'); - } - - // get the (pseudo) context - if (root === 'this' || root === 'context' || type === 'undefined') { - var ctx = app.cache.data; - _.merge(ctx, file.data); - return filter(ctx, segs); - } - // get the file object - if (root === 'file') { - return filter(file, segs); - } - // get the app - if (root === 'app') { - return filter(app, segs); - } - }; - next(); - }; -}; - -function filter(obj, prop) { - var omitKeys = ['debug', '_contents', 'fn']; - if (typeof prop !== 'string' || prop === '') { - return _.omit(_.cloneDeep(obj), omitKeys); - } - return _.omit(_.cloneDeep(get(obj, prop)), omitKeys); -} diff --git a/lib/middleware/dest.js b/lib/middleware/dest.js deleted file mode 100644 index 7556fcfb..00000000 --- a/lib/middleware/dest.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Prime the `file.data.dest` object. - */ - -module.exports = function(file, next) { - file.data.dest = file.data.dest || {}; - next(); -}; diff --git a/lib/middleware/diff.js b/lib/middleware/diff.js deleted file mode 100644 index f5d04b53..00000000 --- a/lib/middleware/diff.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); -var relative = require('relative'); - -/** - * Show a diff comparison of pre- and post-render content. - */ - -module.exports = function(app) { - return function (file, next) { - var diff = app.get('argv.diff'); - - if (!diff) return next(); - console.log(); - console.log('- end -'); - console.log('---------------------------------------------------------------'); - console.log(relative(file.path)); - console.log(); - - if (file.content === '') { - console.log(chalk.gray(' no content.')); - } else if (file.content === file.orig) { - console.log(chalk.gray(' content is unchanged.')); - } else { - app.diff(file.orig, file.content, 'diffLines'); - } - next(); - }; -}; diff --git a/lib/middleware/engine.js b/lib/middleware/engine.js deleted file mode 100644 index 88f358e5..00000000 --- a/lib/middleware/engine.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Set the engine to use - */ - -module.exports = function engine_(file, next) { - file.options.engine = file.options.engine || file.ext || '.md'; - next(); -}; diff --git a/lib/middleware/examples.js b/lib/middleware/examples.js deleted file mode 100644 index 00be4c9a..00000000 --- a/lib/middleware/examples.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -var extract = require('gfm-code-blocks'); - -/** - * Detect the layout to use - */ - -module.exports = function (app) { - return function (file, next) { - app.cache.data.examples = app.cache.data.examples || {}; - var examples = {}; - - var str = file.content; - extract(str).forEach(function (block) { - block.orig = block.block; - var m = /^\/\/\s*example\.([^\n]+)([\s\S]+)/.exec(block.code); - if (!m) return next(); - var name = m[1]; - examples[name] = examples[name] || []; - file.content = file.content.split(block.block).join(''); - block.block = '```js\n' + m[2] + '\n```\n'; - examples[name].push(block); - }); - - app.cache.data.examples = examples; - next(); - }; -}; diff --git a/lib/middleware/ext.js b/lib/middleware/ext.js deleted file mode 100644 index 06e631f0..00000000 --- a/lib/middleware/ext.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('../utils'); - -/** - * Ensure that `ext` is on the file object. - */ - -module.exports = function(file, next) { - if (!file.hasOwnProperty('data')) { - throw new Error('ext middleware: file object should have a `data` property.'); - } - - if (!file.data.hasOwnProperty('src')) { - throw new Error('ext middleware: file.data object should have a `src` property.'); - } - - file.ext = file.ext ? utils.formatExt(file.ext) : (file.data.src.ext || path.extname(file.path)); - next(); -}; diff --git a/lib/middleware/layout.js b/lib/middleware/layout.js deleted file mode 100644 index 764b5c6e..00000000 --- a/lib/middleware/layout.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Detect the layout to use - */ - -module.exports = function(file, next) { - file.layout = file.layout - || file.data.layout - || file.locals.layout - || file.options.layout; - next(); -}; diff --git a/lib/middleware/lint-after.js b/lib/middleware/lint-after.js deleted file mode 100644 index 20c39542..00000000 --- a/lib/middleware/lint-after.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -var extend = require('extend-shallow'); - -/** - * Lints post-render content to find potential errors - * or incorrect content. - */ - -module.exports = function(app) { - var config = extend({}, app.options, app.get('argv')); - var diff = config.diff; - var lint = config.lint; - - return function (file, next) { - if (!lint) return next(); - var str = file.content, error = {}; - - if (/\[object Object\]/.test(str)) { - // only show the last one or two path segments - error.path = file.path.split(/[\\\/]/).slice(-2).join('/'); - error.message = '`[object Object]`'; - error.content = str; - - if (diff) { - app.diff(str, file.orig); - } - app.union('messages.errors', error); - } - next(); - }; -}; diff --git a/lib/middleware/lint.js b/lib/middleware/lint.js deleted file mode 100644 index 18203136..00000000 --- a/lib/middleware/lint.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -/** - * Resolve conflicts between helpers and data - * properties before rendering. - */ - -var lint = require('lint-templates'); - -module.exports = function(app) { - return function (file, next) { - lint(app, file); - next(); - }; -}; diff --git a/lib/middleware/matter.js b/lib/middleware/matter.js deleted file mode 100644 index 67fbd82c..00000000 --- a/lib/middleware/matter.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -var parser = require('parser-front-matter'); -var extend = require('extend-shallow'); - -/** - * Default middleware for parsing front-matter - */ - -module.exports = function (app) { - return function (file, next) { - var opts = extend({}, file.options, app.option('matter')); - parser.parse(file, opts, function (err) { - if (err) return next(err); - next(); - }); - }; -}; diff --git a/lib/middleware/multi-toc.js b/lib/middleware/multi-toc.js deleted file mode 100644 index 8e9be546..00000000 --- a/lib/middleware/multi-toc.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var glob = require('globby'); -var relative = require('relative'); -var toc = require('markdown-toc'); -var mdu = require('markdown-utils'); - -/** - * Generate a Table of Contents for a glob of files. - */ - -module.exports = function multitoc_(file, next) { - var str = file.content; - var i = str.indexOf('') + 3); - var pattern = strip(tag); - - var args = toArgs(pattern); - var files = []; - - if (args.length && args[1]) { - var opts = args[1] || {}; - files = glob.sync(args[0], opts).map(function (fp) { - return path.join(opts.cwd || process.cwd(), fp); - }); - } else { - files = glob.sync(args[0]); - } - - if (!files.length) return next(); - var res = files.map(generate).join('\n'); - file.content = str.split(tag).join(res); - next(); -}; - -function generate(fp, options) { - options = options || {}; - fp = path.join(options.cwd || process.cwd(), fp); - var str = fs.readFileSync(fp, 'utf8'); - var first = str.split('\n')[0].trim().slice(2).trim(); - - // don't generate a TOC for a template - if (/\{%=/.test(first)) return ''; - var res = ''; - - res += mdu.h2(mdu.link(first, relative(fp))); - res += '__AFTER__'; - res += '\n'; - - var table = toc(str, { - linkify: function(tok, heading) { - var url = relative(fp); - url += '/#'; - url += toc.slugify(heading); - tok.content = mdu.link(tok.content, url); - return tok; - } - }); - - res += table.content; - res += '\n'; - - res = res.split(/\n{2,}/).join('\n'); - res = res.split('__AFTER__').join('\n'); - return res; -} - -function strip(str) { - str = str.replace(//, ''); - return str.trim(); -} - -function toArgs(pattern) { - var args = pattern.split(',').filter(Boolean); - args[0] = args[0].trim().replace(/^['"]|['"]$/g, ''); - args[1] = args[1] ? JSON.parse(args[1].trim()) : null; - return args; -} diff --git a/lib/middleware/props.js b/lib/middleware/props.js deleted file mode 100644 index d97fdba1..00000000 --- a/lib/middleware/props.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Prime the `file` object with properties that - * can be extended in plugins. - */ - -module.exports = function props_(file, next) { - file.options = file.options || {}; - file.locals = file.locals || {}; - file.data = file.data || {}; - next(); -}; diff --git a/lib/middleware/readme.js b/lib/middleware/readme.js deleted file mode 100644 index c79394b1..00000000 --- a/lib/middleware/readme.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -/** - * Set the dest path to `README.md` for `.verb.md` templates - */ - -module.exports = function(app) { - return function(file, next) { - if (app.isFalse('readme') || app.isTrue('noreadme')) { - return next(); - } - - if (file.readme === false || file.noreadme === true) { - return next(); - } - - if (/\.(?:verb(rc)?|readme)\.md$/i.test(file.path)) { - file.path = 'README.md'; - } - next(); - }; -}; diff --git a/lib/middleware/src.js b/lib/middleware/src.js deleted file mode 100644 index 8f001a13..00000000 --- a/lib/middleware/src.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -var parse = require('parse-filepath'); -var path = require('path'); - -/** - * Set properties on `file.data.src` to use in plugins, - * other middleware, helpers, templates etc. - */ - -module.exports = function(file, next) { - var orig = file.options.originalPath; - - file.data.src = file.data.src || {}; - file.data.src.path = orig; - - // look for native `path.parse` method first - var parsed = typeof path.parse === 'function' - ? path.parse(orig) - : parse(orig); - - file.data.src.dirname = parsed.dir; - file.data.src.filename = parsed.name; - file.data.src.basename = parsed.base; - file.data.src.extname = parsed.ext; - file.data.src.ext = parsed.ext; - - file.data.process = {}; - file.data.process.cwd = function () { - return process.cwd(); - }; - - file.data.resolve = path.resolve; - next(); -}; diff --git a/lib/pkg.json b/lib/pkg.json deleted file mode 100644 index 5fcbabed..00000000 --- a/lib/pkg.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "", - "description": "", - "version": "", - "homepage": "", - "authors": [ - { - "name": "", - "email": "", - "url": "" - } - ], - "author": { - "name": "", - "email": "", - "url": "" - }, - "contributors": [], - "repository": { - "type": "git", - "url": "" - }, - "bugs": { - "url": "" - }, - "licenses": [ - { - "type": "", - "url": "" - } - ], - "license": { - "type": "MIT", - "url": "" - }, - "files": [], - "main": "", - "bin": {}, - "engines": {}, - "scripts": {}, - "dependencies": {}, - "devDependencies": {}, - "keywords": [], - "verb": { - "data": {} - }, - "github": { - "user": "", - "repo": "" - } -} diff --git a/lib/plugins/comments.js b/lib/plugins/comments.js deleted file mode 100644 index c2729c5f..00000000 --- a/lib/plugins/comments.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -var extend = require('extend-shallow'); -var through = require('through2'); -var PluginError = require('plugin-error'); - -module.exports = function() { - var opts = extend({}, this.options, this.get('argv')); - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - - try { - // strip code comments - var str = file.contents.toString(); - str = stripHbsComments(str, opts); - str = stripTmplComments(str, opts); - str = stripHtmlComments(str, opts); - - // rebuffer contents - file.contents = new Buffer(str); - this.push(file); - return cb(); - } catch (err) { - this.emit('error', new PluginError('comments plugin', err, {stack: true})); - return cb(); - } - }); -}; - -function stripHbsComments(str, opts) { - return strip(str, '{{!', '}}', opts); -} - -function stripTmplComments(str, opts) { - return strip(str, '{{#', '#}}', opts); -} - -function stripHtmlComments(str, opts) { - str = strip(str, '', opts); - return strip(str, '', opts); -} - -function strip(str, lt, rt, opts) { - if (opts && opts.stripComments === false) return str; - var a = str.indexOf(lt); - var b = str.indexOf(rt, a); - - while (a !== -1 && b !== -1) { - str = str.slice(0, a) + str.slice(b + rt.length); - a = str.indexOf(lt); - b = str.indexOf(rt, a); - } - return str; -} diff --git a/lib/plugins/conflicts.js b/lib/plugins/conflicts.js deleted file mode 100644 index 513845dd..00000000 --- a/lib/plugins/conflicts.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; - -var PluginError = require('plugin-error'); -var symbol = require('log-symbols'); -var through = require('through2'); -var yellow = require('ansi-yellow'); -var red = require('ansi-red'); -var _ = require('lodash'); - -module.exports = function(options) { - var config = this.get('argv') || {}; - options = _.extend({}, config, options); - var app = this; - - if (config.conflicts && config.verbose) { - console.log(); - console.log(yellow('Checking for conflicts…')); - } - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - - if (config.nocheck) { - this.push(file); - return cb(); - } - - try { - var str = file.contents.toString(); - var res = helpers(str); - var conflicting = problematic(app, file, res, config.verbose); - var h = {}; - - if (!conflicting.length) { - this.push(file); - return cb(); - } - - var ctx = {}; - ctx.options = _.extend({}, app.options, file.options); - ctx.context = file.locals || {}; - ctx.config = app.config; - ctx.app = app; - - file.locals = file.locals || {}; - file.locals.__ = file.locals.__ || {}; - - for (var i = 0; i < conflicting.length; i++) { - var name = conflicting[i]; - file.content = namespace(file.content, name); - var syncFn = app._.helpers.getHelper(name); - if (syncFn) { - h[name] = _.bind(syncFn, ctx); - file.locals.__[name] = _.bind(syncFn, ctx); - app.helpers({__: h}); - delete app._.helpers[name]; - } - - var asyncFn = app._.asyncHelpers.getHelper(name); - if (asyncFn) { - h[name] = _.bind(asyncFn, ctx); - file.locals.__[name] = _.bind(asyncFn, ctx); - app.asyncHelpers({__: h}); - delete app._.asyncHelpers[name]; - } - - if (!asyncFn && !syncFn) { - h[name] = noop(name); - file.locals.__[name] = noop(name); - app.helpers({__: h}); - } - } - - file.contents = new Buffer(file.content); - this.push(file); - return cb(); - } catch (err) { - this.emit('error', new PluginError('conflicts plugin', err, {stack: true})); - return cb(); - } - }); -}; - -function noop(name) { - return function () { - var msg = ''; - msg += 'ERROR! Cannot find the {%= '; - msg += name; - msg += '() %} helper. Helpers may be '; - msg += 'registered using `app.helper()`.'; - // console.log(red(msg)); - }; -} - -function namespace(str, name) { - return str.split('{%= ' + name).join('{%= __.' + name); -} - -function helpers(str) { - var re = /\{%=\s*((?!__\.)[\w.]+)(?:\(\)|\(([^)]*?)\))\s*%}/gm; - var res = [], - match; - - while (match = re.exec(str)) { - if (res.indexOf(match[1]) === -1) { - res.push(match[1].trim()); - } - } - return res; -} - -function problematic(app, file, helpers, verbose) { - var dataKeys = Object.keys(app.cache.data); - dataKeys = _.union(dataKeys, Object.keys(file.data)); - var registered = Object.keys(app._.asyncHelpers); - registered = _.union(registered, Object.keys(app._.helpers)); - - var h = [], - d = []; - var len = helpers.length; - - while (len--) { - var helper = helpers[len]; - // if the helper name is also a data prop, it's a conflict - if (dataKeys.indexOf(helper) !== -1 && helper.indexOf('.') === -1) { - d.push(helper); - } - - // if the helper is not registered, it's a conflict - if (registered.indexOf(helper) === -1 && helper.indexOf('.') === -1) { - h.push(helper); - } - } - - message(h, d, verbose, file); - return _.union(h, d); -} - -function message(h, d, verbose, file) { - if (!verbose) return; - var fp = file.path.split(/[\\\/]/).slice(-2).join('/'); - - var hlen = h.length; - var dlen = d.length; - - if (hlen > 0) { - console.log(symbol.error, '', red(hlen + ' missing helper' + s(hlen) + ' found in', fp)); - h.forEach(function (ele) { - console.log(' -', ele); - }); - console.log(); - } - - if (dlen > 0) { - console.log(symbol.warning, '', yellow(dlen + ' data/helper conflict' + s(dlen) + ' found in', fp)); - d.forEach(function (ele) { - console.log(' -', ele); - }); - console.log(); - } -} - -function s(len) { - return len > 1 ? 's' : ''; -} diff --git a/lib/plugins/dest.js b/lib/plugins/dest.js deleted file mode 100644 index 5606fd47..00000000 --- a/lib/plugins/dest.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -var path = require('path'); -var through = require('through2'); -var PluginError = require('plugin-error'); -var utils = require('../utils'); - -/** - * Add dest properties to `file.data` - */ - -module.exports = function destPlugin(destDir) { - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - - try { - var dest = file.data.dest || {}; - if (typeof destDir === 'function') { - dest.dirname = destDir(file); - } else if (typeof destDir === 'string') { - dest.dirname = path.resolve(destDir); - } else { - dest.dirname = path.dirname(file.path); - } - - dest.cwd = file.cwd; - dest.base = file.base; - dest.relative = file.relative; - dest.extname = path.extname(file.relative); - dest.basename = path.basename(file.relative); - dest.filename = utils.basename(dest.basename, dest.extname); - dest.path = path.join(dest.dirname, dest.basename); - - file.data.__filename = dest.path; - file.data.__dirname = dest.dirname; - file.data.dest = dest; - - this.push(file); - return cb(); - } catch (err) { - this.emit('error', new PluginError('dest plugin', err, {stack: true})); - return cb(); - } - }); -}; diff --git a/lib/plugins/format.js b/lib/plugins/format.js deleted file mode 100644 index 339353c2..00000000 --- a/lib/plugins/format.js +++ /dev/null @@ -1,85 +0,0 @@ -'use strict'; - -var through = require('through2'); -var Remarkable = require('remarkable'); -var PluginError = require('plugin-error'); -var prettify = require('pretty-remarkable'); -var extend = require('extend-shallow'); - - -module.exports = function(locals) { - var argv = this.get('argv'); - var app = this; - - // add a trailing newline to docs? - var newline = argv.newline || this.config.get('newline'); - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - - try { - if (file.path.indexOf('README.md') === -1 || noformat(app, file, locals, argv)) { - this.push(file); - return cb(); - } - - // pass some extra formatting info to `pretty-remarkable` - var opts = extend({}, locals, file.options); - opts.username = app.get('data.username'); - opts.name = app.get('data.author.name'); - - // prettify - var str = pretty(file.contents.toString(), opts); - str = str.trim() + (newline ? '\n' : ''); - str = fixList(str); - - // rebuffer contents - file.contents = new Buffer(str); - this.push(file); - return cb(); - } catch(err) { - this.emit('error', new PluginError('formatter plugin', err, {stack: true})); - return cb(); - } - }); -}; - -/** - * Fix list formatting - */ - -function fixList(str) { - str = str.replace(/([ ]{1,4}[+-] \[?[^)]+\)?)\n\n\* /gm, '$1\n* '); - str = str.split('__{_}_*').join('**{*}**'); - return str; -} - -/** - * Instantiate `Remarkable` and use the `prettify` plugin - * on the given `str`. - * - * @param {String} `str` - * @param {Object} `options` - * @return {String} - */ - -function pretty(str, options) { - return new Remarkable(options) - .use(prettify) - .render(str); -} - -/** - * Push the `file` through if the user has specfied - * not to format it. - */ - -function noformat(app, file, locals, argv) { - return app.isTrue('noformat') || app.isFalse('format') - || file.noformat === true || file.format === false - || locals.noformat === true || locals.format === false - || argv.noformat === true || argv.format === false; -} diff --git a/lib/plugins/index.js b/lib/plugins/index.js deleted file mode 100644 index 23b2930d..00000000 --- a/lib/plugins/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('export-files')(__dirname); diff --git a/lib/plugins/init.js b/lib/plugins/init.js deleted file mode 100644 index 772bdd39..00000000 --- a/lib/plugins/init.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -var gutil = require('gulp-util'); -var PluginError = gutil.PluginError; -var through = require('through2'); - -module.exports = function () { - var app = this; - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - if (file.isStream()) { - this.emit('error', new PluginError('init plugin', 'Streaming is not supported.')); - return cb(); - } - - try { - var stream = this; - file.content = file.contents.toString(); - app.handle('onLoad', file, function (err) { - if (err) { - stream.emit('error', new PluginError('renderFile', err, {stack: true})); - cb(err); - return; - } - }); - this.push(file); - cb(); - } catch (err) { - this.emit('error', new PluginError('init plugin', err, {stack: true})); - cb(); - } - }); -}; diff --git a/lib/plugins/lint.js b/lib/plugins/lint.js deleted file mode 100644 index 891d2525..00000000 --- a/lib/plugins/lint.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var PluginError = require('plugin-error'); -var lint = require('lint-templates'); -var through = require('through2'); - -module.exports = function(options) { - var app = this; - return through.obj(function (file, enc, cb) { - try { - lint(app, file); - this.push(file); - return cb(); - } catch(err) { - this.emit('error', new PluginError('lint plugin', err, {stack: true})); - return cb(); - } - }); -}; diff --git a/lib/plugins/reflinks.js b/lib/plugins/reflinks.js deleted file mode 100644 index 01d585d0..00000000 --- a/lib/plugins/reflinks.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -var through = require('through2'); -var PluginError = require('plugin-error'); -var reflinks = require('helper-reflinks'); -var extend = require('extend-shallow'); -var isTrue = require('is-true'); - -/** - * Expose `reflinks` plugin - */ - -module.exports = function(options) { - options = options || {}; - var config = this.config && this.config.get('reflinks') || {}; - var argv = this.get('argv'); - var app = this; - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - - var disabled = !enabled(app, file, options, argv, config); - if (disabled || !config) { - this.push(file); - return cb(); - } - - try { - app.union('reflinks', file.data.reflinks || []); - var list = app.get('reflinks') || []; - - if (!list || !list.length) { - this.push(file); - return cb(); - } - - var stream = this; - var str = file.contents.toString(); - - reflinks(list, function (err, res) { - if (err) { - stream.emit('error', new PluginError('reflinks plugin', err)); - return cb(); - } - - file.contents = new Buffer(str + '\n' + res); - stream.push(file); - return cb(); - }); - - } catch (err) { - this.emit('error', new PluginError('reflinks plugin', err)); - return cb(); - } - }); -}; - -/** - * Push the `file` through if the user has specfied - * not to generate reflinks. - */ - -function enabled(app, file, options, argv) { - var template = extend({}, file.locals, file.options, file.data); - return isTrue(argv, 'reflinks') - || isTrue(template, 'reflinks') - || isTrue(options, 'reflinks') - || isTrue(app.options, 'reflinks'); -} diff --git a/lib/plugins/render.js b/lib/plugins/render.js deleted file mode 100644 index bb8d9173..00000000 --- a/lib/plugins/render.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var PluginError = require('plugin-error'); -var through = require('through2'); -var _ = require('lodash'); - -/** - * Expose `render` plugin - */ - -module.exports = function (locals) { - var app = this; - var opts = _.extend({showStack: true}, this.options); - - locals = locals || {}; - locals.options = locals.options || {}; - - return through.obj(function (file, enc, cb) { - if (file.isNull()) { - this.push(file); - return cb(); - } - - locals = _.merge({}, locals, file.locals); - locals.options = _.merge({}, app.options, locals.options); - file.content = file.contents.toString(); - - try { - var stream = this; - app.render(file, locals, function (err, content) { - if (err) { - stream.emit('error', new PluginError('render plugin', err, opts)); - return cb(); - } - file.contents = new Buffer(content); - stream.push(file); - return cb(); - }); - } catch (err) { - this.emit('error', new PluginError('render plugin', err, opts)); - return cb(); - } - }); -}; diff --git a/lib/questions.js b/lib/questions.js new file mode 100644 index 00000000..0407ddde --- /dev/null +++ b/lib/questions.js @@ -0,0 +1,106 @@ +'use strict'; + +const defaultLayout = app => { + const layout = app.pkg.get([app._name, 'layout']); + if (typeof layout === 'string' && layout.trim() !== 'default') { + return layout; + } + const name = app.pkg.get('name'); + if (/^generate-/.test(name)) { + return 'generator'; + } + if (/^updater-/.test(name)) { + return 'updater'; + } + if (/^helper-/.test(name)) { + return 'helper'; + } + if (/^assemble-/.test(name)) { + return 'assemble'; + } + if (/^verb-/.test(name)) { + return 'verb'; + } + return 'default'; +}; + +/** + * Build the list of `config.*` options to prompt the user about + */ + +const buildChoices = app => app.questions.queue.reduce((acc, key) => { + if (key.indexOf('config.') !== 0) { + return acc; + } + + acc.push(key.slice('config.'.length)); + return acc; +}, []); + +module.exports = (app, options) => { + + /** + * Config questions + */ + + app.questions + .set('config.layout', 'What layout would you like to use?', { + default: defaultLayout(app) + }) + .set('config.toc', 'Add Table of Contents to README.md?', { + type: 'confirm', + default: app.pkg.get('verb.toc') || false + }) + .set('config.plugins', 'Plugins to use (comma-separated):', { + default: app.pkg.get('verb.plugins') || ['gulp-format-md'] + }) + .set('config.tasks', 'Tasks or generators to run (comma-separated)', { + default: app.pkg.get('verb.tasks') || ['readme'] + }) + .set('config.lint.reflinks', 'Do you want to lint for missing reflinks and add them to verb config?', { + type: 'confirm' + }); + + /** + * Ask the user if they want to install plugins, if any were specified + */ + + app.question('after.plugins', 'Plugins need to be installed, want to do that now?', { + type: 'confirm' + }); + + /** + * Init questions + */ + + app.question('init.preferences', 'Would you like to set defaults for this project?', { + type: 'confirm', + next(answer, question, answers, cb) { + // Ensure `init` questions aren't asked again + delete answers.init; + + if (answer === true) { + app.ask('init.choose', cb); + } else { + cb(null, answers); + } + } + }); + + app.choices('init.choose', 'Which options would you like to set?', { + choices: buildChoices(app), + save: false, + next(answer, question, answers, cb) { + // Ensure `init` questions aren't asked again + delete answers.init; + + if (typeof answer === 'undefined' || answer.length === 0) { + cb(null, answers); + return; + } + + const choices = answer.map(value => 'config.' + value); + app.ask(choices, cb); + } + }); +}; diff --git a/lib/stack.js b/lib/stack.js deleted file mode 100644 index f10acb4f..00000000 --- a/lib/stack.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var through = require('through2'); -var drafts = require('gulp-drafts'); -var es = require('event-stream'); -var vfs = require('vinyl-fs'); -var _ = require('lodash'); -var cache = {}; - -/** - * Local dependencies - */ - -var plugins = require('./plugins'); - -/** - * Default `src` plugins to run. - * - * To disable a plugin: - * - * ```js - * app.disable('src:foo plugin'); - * ``` - */ - -exports.src = function(app, glob, opts) { - opts = _.merge({}, app.options, opts); - cache = opts; - - return createStack(app, { - 'src:vfs': vfs.src(glob, opts), - 'src:init': plugins.init.call(app, opts), - 'src:lint': plugins.lint.call(app, opts), - 'src:conflicts': plugins.conflicts.call(app, opts), - 'src:drafts': drafts.call(app, opts) - }); -}; - -/** - * Default `dest` plugins to run. - * - * To disable a plugin: - * - * ```js - * app.disable('dest:bar plugin'); - * ``` - */ - -exports.dest = function (app, dest, opts) { - opts = _.merge({}, app.options, cache, opts); - opts.locals = opts.locals || {}; - - return createStack(app, { - 'dest:paths': plugins.dest.call(app, dest, opts.locals, opts), - 'dest:render': plugins.render.call(app, opts.locals, opts), - 'dest:reflinks': plugins.reflinks.call(app, opts.locals, opts), - 'dest:comments': plugins.comments.call(app), - 'dest:format': plugins.format.call(app, opts.locals, opts), - 'dest:vfs': vfs.dest(dest, opts) - }); -}; - -/** - * Create the default plugin stack based on user settings. - * - * Disable a plugin by passing the name of the plugin + ` plugin` - * to `app.disable()`, - * - * **Example:** - * - * ```js - * app.disable('src:foo plugin'); - * app.disable('src:bar plugin'); - * ``` - */ - -function createStack(app, plugins) { - if (app.enabled('minimal config')) { - return es.pipe.apply(es, []); - } - function enabled(acc, plugin, name) { - if (plugin == null) { - acc.push(through.obj()); - } - if (app.enabled(name + ' plugin')) { - acc.push(plugin); - } - return acc; - } - var arr = _.reduce(plugins, enabled, []); - return es.pipe.apply(es, arr); -} diff --git a/lib/tasks.js b/lib/tasks.js new file mode 100644 index 00000000..bbda95fc --- /dev/null +++ b/lib/tasks.js @@ -0,0 +1,80 @@ +'use strict'; + +const utils = require('./utils'); + +module.exports = (app, ctx, argv) => { + if (argv.init === true) { + return []; + } + + const configFile = ctx.env.configFile; + let configExists; + let configTasks; + + // Determine the tasks to run (returns the first value that isn't `["default"]` or `[]`) + let tasks = utils.getTasks(configFile, [ + argv._, // Command line + ctx.pkgConfig.tasks, // Set in package.json + app.store.get('defaultTasks') // Stored user-defined "default" tasks + ]); + + tasks = tasks.map(task => { + let isDefaults = false; + + if (task.indexOf('new') === 0 || task.indexOf('store') === 0) { + isDefaults = true; + task = 'defaults.' + task; + + } else if (task === 'list') { + isDefaults = true; + task = 'defaults:list'; + + } else if (task === 'init') { + isDefaults = true; + task = 'defaults:init'; + + } else if (task === 'format') { + isDefaults = true; + task = 'defaults:format'; + + } else if (task === 'diff') { + isDefaults = true; + task = 'defaults:diff'; + } + + if (isDefaults === true) { + app.enable('silent'); + } + return task; + }); + + if (tasks.length === 1 && tasks[0] === 'default') { + configExists = utils.exists(configFile); + configTasks = app.pkg.get('verb.tasks'); + + // If a `verbfile.js` or custom configFile exists, return tasks + if (configExists) { + if (configTasks && configTasks.length) { + app.pkg.logWarning('ignoring tasks defined in package.json:', configTasks); + } + return tasks; + } + + if (configTasks && configTasks.length) { + return configTasks; + } + + const verbmd = utils.exists('.verb.md'); + + // If a `.verb.md` exists, but no verbfile.js, set `readme` as the default + if (verbmd && !configExists) { + return ['verb-generate-readme']; + } + + // If no verbfile.js, and no `.verb.md`, ask the user if they want a `.verb.md` + if (!verbmd) { + return ['defaults.new:prompt-verbmd'].concat(tasks); + } + } + return tasks; +}; diff --git a/lib/templates/_verb.md b/lib/templates/_verb.md new file mode 100644 index 00000000..ca944cf1 --- /dev/null +++ b/lib/templates/_verb.md @@ -0,0 +1,5 @@ +## Usage + +```js +import {%= alias %} from '{%= name %}'; +``` diff --git a/lib/templates/_verbrc.json b/lib/templates/_verbrc.json new file mode 100644 index 00000000..77f8fd50 --- /dev/null +++ b/lib/templates/_verbrc.json @@ -0,0 +1,7 @@ +{ + "data": {}, + "helpers": [], + "options": {}, + "plugins": [], + "tasks": [] +} \ No newline at end of file diff --git a/lib/templates/readme.md b/lib/templates/readme.md new file mode 100644 index 00000000..b4670c07 --- /dev/null +++ b/lib/templates/readme.md @@ -0,0 +1,9 @@ +# <%= ask("name", "Project name?") %> + +<%= ask("description", "Project description") %> + +## Usage + +```js +import {%= varname %} from '<%= name %>'; +``` diff --git a/lib/templates/verbfile.js b/lib/templates/verbfile.js new file mode 100644 index 00000000..c86585c9 --- /dev/null +++ b/lib/templates/verbfile.js @@ -0,0 +1,14 @@ +--- +layout: false +--- +'use strict'; + +module.exports = verb => { + verb.use(require('verb-generate-readme')); + + verb.helper('foo', input => { + return input; + }); + + verb.task('default', ['readme']); +}; diff --git a/lib/transforms/config/del.js b/lib/transforms/config/del.js deleted file mode 100644 index f66b2175..00000000 --- a/lib/transforms/config/del.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var mm = require('micromatch'); -var bold = require('ansi-bold'); - -/** - * Delete a value from the config store. - * - * ```sh - * $ --del foo - * - * # delete multiple values - * $ --del foo,bar,baz - * ``` - */ - -module.exports = function(app) { - var del = app.get('argv.del'); - var config = app.config; - var keys = mm(Object.keys(config.data), del); - - if (keys.length) { - config.omit.apply(config, keys); - console.log('deleted:', bold(keys.join(', '))); - } -}; diff --git a/lib/transforms/config/get.js b/lib/transforms/config/get.js deleted file mode 100644 index f143da20..00000000 --- a/lib/transforms/config/get.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -var bold = require('ansi-bold'); -var cyan = require('ansi-cyan'); - -/** - * Get a value from the config store. - * - * ```sh - * $ --get one - * # or - * $ --get one,two,three - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var get = app.get('argv.get'); - - if (get) { - if (get === true || get === 'true') { - console.log(cyan('config config:'), stringify(config.data)); - } else if (get.indexOf(',') !== -1) { - get.split(',').forEach(function (val) { - console.log(val, '=', stringify(config.get(val))); - }); - } else { - console.log(get, '=', stringify(config.get(get))); - } - } -}; - -function stringify(val) { - return bold(JSON.stringify(val)); -} diff --git a/lib/transforms/config/index.js b/lib/transforms/config/index.js deleted file mode 100644 index bd259188..00000000 --- a/lib/transforms/config/index.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var Config = require('data-store'); -var config = require('export-files')(__dirname); - -/** - * Initialize a global config store, for persisting data - * that may be reused across projects. - * - * Initialized in the `init` transform. - */ - -module.exports = function(verb) { - verb.config = new Config('verb'); - - verb.transform('set', config.set); - verb.transform('get', config.get); - verb.transform('del', config.del); - verb.transform('option', config.option); - verb.transform('union', config.union); -}; diff --git a/lib/transforms/config/option.js b/lib/transforms/config/option.js deleted file mode 100644 index 85035f87..00000000 --- a/lib/transforms/config/option.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** - * Persist a value on the config store. - * - * ```sh - * $ --option one=abc - * #=> {one: 'abc'} - * - * $ --option one - * #=> {one: true} - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var args; - - var option = app.get('argv.option'); - if (option) { - args = option.split('='); - if (args.length === 2) { - config.set(args[0], args[1]); - } else { - config.set(args[0], true); - } - } - - var enable = app.get('argv.enable'); - if (enable) { - config.set(enable, true); - } - - var disable = app.get('argv.disable'); - if (disable) { - config.set(disable, false); - } -}; diff --git a/lib/transforms/config/set.js b/lib/transforms/config/set.js deleted file mode 100644 index 773aec9c..00000000 --- a/lib/transforms/config/set.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -/** - * Persist a value on the config store. - * - * ```sh - * $ --set one=abc - * #=> {one: 'abc'} - * - * $ --set one - * #=> {one: true} - * ``` - */ - -module.exports = function(app) { - var config = app.config; - var args; - - var set = app.get('argv.set'); - if (set) { - args = set.split('='); - if (args.length === 2) { - config.set(args[0], args[1]); - } else { - config.set(args[0], true); - } - } -}; diff --git a/lib/transforms/config/union.js b/lib/transforms/config/union.js deleted file mode 100644 index 0abd17dc..00000000 --- a/lib/transforms/config/union.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var union = require('arr-union'); - -/** - * Get a value from the config store. - * - * ```sh - * $ --union one=a,b,c - * #=> {one: ['a', 'b', 'c']} - * - * $ --union one=d - * #=> {one: ['a', 'b', 'c', 'd']} - * ``` - */ - -module.exports = function(app) { - var arr = app.get('argv.union'); - var config = app.config; - var args; - - if (arr) { - args = arr.split('='); - if (args.length > 1) { - var val = config.get(args[1]); - args[2] = args[2].split(','); - config.set(args[1], union(val, args[2])); - } - } -}; diff --git a/lib/transforms/env/author.js b/lib/transforms/env/author.js deleted file mode 100644 index 6ecff18f..00000000 --- a/lib/transforms/env/author.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -var isPlainObject = require('is-plain-object'); -var author = require('parse-author'); - -/** - * Called in the `init` transform. Adds an `author` - * property to the context, or normalizes the existing one. - */ - -module.exports = function(app) { - var res = app.get('data.author'); - if (isPlainObject(res)) return; - - if (typeof res === 'string') { - app.data({author: author(res)}); - } else { - app.data({author: {}}); - } -}; diff --git a/lib/transforms/env/cwd.js b/lib/transforms/env/cwd.js deleted file mode 100644 index 46b0899c..00000000 --- a/lib/transforms/env/cwd.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -/** - * Get/set the current working directory - * - * ```js - * console.log(verb.cwd); - * //=> /dev/foo/bar/ - * ``` - * Or set: - * - * ```js - * verb.cwd = 'foo'; - * ``` - */ - -module.exports = function(app) { - var cwd = app.option('cwd') || process.cwd(); - - Object.defineProperty(app, 'cwd', { - configurable: true, - get: function () { - return cwd; - }, - set: function (val) { - cwd = val; - } - }); -}; diff --git a/lib/transforms/env/env.js b/lib/transforms/env/env.js deleted file mode 100644 index 3c54ac4f..00000000 --- a/lib/transforms/env/env.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -var path = require('path'); -var merge = require('lodash')._.merge; - -/** - * Getter for getting stored values from `verb.env`, a read-only - * object that stores the project's package.json before it's modified. - * - * ```js - * console.log(verb.env.name); - * //=> 'my-project' - * ``` - * - * @return {*} The value of specified property. - * @api public - */ - -module.exports = function(app) { - var env = app.env || app.option('env') || require(path.resolve('package.json')); - merge(env, (env.verb && env.verb.data) || {}); - - Object.defineProperty(app, 'env', { - get: function () { - return env; - }, - set: function () { - console.log('verb.env is read-only and cannot be modified.'); - } - }); -}; diff --git a/lib/transforms/env/files.js b/lib/transforms/env/files.js deleted file mode 100644 index 80117b26..00000000 --- a/lib/transforms/env/files.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var mm = require('micromatch'); -var glob = require('globby'); - -/** - * Loads files from the root of the current project. - * Returns matching function bound to the list of files. - * - * ```js - * console.log(verb.files('*.js')); - * //=> ['foo.js', 'bar.js'] - * ``` - */ - -module.exports = function(verb) { - var patterns = ['*', 'lib/*', 'test/*']; - var files = glob.sync(patterns, {dot: true}); - - verb.files = function (pattern, options) { - return mm(files, pattern, options); - }; - - verb.fileExists = function (pattern, options) { - var res = verb.files(pattern, options); - return !!(res && res.length); - }; -}; diff --git a/lib/transforms/env/fork.js b/lib/transforms/env/fork.js deleted file mode 100644 index 0e1f6080..00000000 --- a/lib/transforms/env/fork.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -/** - * Adds a `fork` URL to the context - */ - -module.exports = function(app) { - var repo = app.get('data.repository'); - if (! repo) { - return; - } - var url = repo.url.replace(/\.git$/, ''); - url = url.replace(/git:\/\//, 'https://'); - app.data({fork: url + '/fork'}); -}; diff --git a/lib/transforms/env/git.js b/lib/transforms/env/git.js deleted file mode 100644 index 92440275..00000000 --- a/lib/transforms/env/git.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var username = require('git-user-name'); - -/** - * Called in the `username` transform, if a `username` - * cannot be determined from easier means, this attempts - * to get the `user.name` from global `.git config` - */ - -module.exports = function(verb) { - if (!verb.get('data.git.username')) { - verb.set('data.git.username', username()); - } -}; diff --git a/lib/transforms/env/github.js b/lib/transforms/env/github.js deleted file mode 100644 index 5adbc3d0..00000000 --- a/lib/transforms/env/github.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -var parse = require('parse-github-url'); - -/** - * Called in the `init` transform. Adds a `github` - * property to the context. - * - * @param {Object} `verb` - */ - -module.exports = function(verb) { - var repo = verb.get('data.repository'); - var url = (repo && typeof repo === 'object') - ? repo.url - : repo; - - var github = parse(url); - if (github && Object.keys(github).length) { - verb.data({github: github}); - } -}; diff --git a/lib/transforms/env/ignore.js b/lib/transforms/env/ignore.js deleted file mode 100644 index cda93f2a..00000000 --- a/lib/transforms/env/ignore.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var parse = require('parse-gitignore'); -var union = require('lodash')._.union; -var utils = require('../../utils'); - -/** - * Read `.gitignore` and add patterns to - * `options.ignore` - */ - -module.exports = function(verb) { - if (typeof verb.cache.data.ignore === 'undefined') { - var ignore = union(verb.option('ignore'), verb.env.ignore); - verb.data({ignore: gitignore(verb.cwd, '.gitignore', ignore)}); - } -}; - -/** - * Parse the local `.gitignore` file and add - * the resulting ignore patterns. - */ - -function gitignore(cwd, fp, arr) { - fp = path.resolve(cwd, fp); - if (!fs.existsSync(fp)) { - return utils.defaultIgnores; - } - var str = fs.readFileSync(fp, 'utf8'); - return parse(str, arr); -} diff --git a/lib/transforms/env/index.js b/lib/transforms/env/index.js deleted file mode 100644 index 3a9b8a9b..00000000 --- a/lib/transforms/env/index.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); - -/** - * Load the project environment. Starting with - * package.json and .gitignore. - */ - -// module.exports = function(app) { -// app.transform('env env', env.env); -// app.transform('env ignore', env.ignore); -// app.transform('env files', env.files); -// app.transform('env keys', env.keys); -// app.transform('env github', env.github); -// app.transform('env travis', env.travis); -// app.transform('env user', env.user); -// app.transform('env author', env.author); -// app.transform('env username', env.username); -// app.transform('env license', env.license); -// app.transform('env licenses', env.licenses); -// }; diff --git a/lib/transforms/env/keys.js b/lib/transforms/env/keys.js deleted file mode 100644 index e3fce9e6..00000000 --- a/lib/transforms/env/keys.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -/** - * Adds `Object.keys()` to `verb.cache.data`. Can be used - * for conflict resolution between helpers and context properties. - */ - -module.exports = function(verb) { - if (!verb.cache.data.hasOwnProperty('keys')) { - verb.cache.data.keys = Object.keys(verb.env); - } -}; diff --git a/lib/transforms/env/missing.js b/lib/transforms/env/missing.js deleted file mode 100644 index e332f605..00000000 --- a/lib/transforms/env/missing.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -/** - * Called in the `init` transform to ensure that certain - * fields are on the context. - * - * @param {Object} `verb` - */ - -module.exports = function(verb) { - verb.cache.missing = verb.cache.missing || {}; - - if (!verb.cache.data.hasOwnProperty('licenses')) { - verb.union('messages.missing.data', ['licenses']); - } - - if (!verb.cache.data.hasOwnProperty('license')) { - verb.union('messages.missing.data', ['license']); - } -}; diff --git a/lib/transforms/env/paths.js b/lib/transforms/env/paths.js deleted file mode 100644 index c1562bfb..00000000 --- a/lib/transforms/env/paths.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -/** - * Prime the `verb.paths` object. - */ - -module.exports = function(app) { - app.cache.paths = app.cache.paths || {}; -}; diff --git a/lib/transforms/env/pkg.js b/lib/transforms/env/pkg.js deleted file mode 100644 index 118b8646..00000000 --- a/lib/transforms/env/pkg.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var utils = require('../../utils'); -var cache = {}; - -/** - * Called in the `init` transform. A read-only copy of this data is also - * stored on `verb.env` - * - * @param {Object} `verb` - */ - -module.exports = function(verb) { - var filename = verb.option('config') || 'package.json'; - - verb.data(filename, function (fp) { - return cache[fp] || (cache[fp] = utils.tryRequire(fp) || {}); - }); -}; diff --git a/lib/transforms/env/templates.js b/lib/transforms/env/templates.js deleted file mode 100644 index 38110d2a..00000000 --- a/lib/transforms/env/templates.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -var path = require('path'); - -/** - * Get/set the current working directory - * - * ```js - * console.log(verb.templates); - * //=> /dev/foo/bar/ - * ``` - * Or set: - * - * ```js - * verb.templates = 'foo'; - * ``` - */ - -module.exports = function(app) { - var dir = app.option('templates'); - if (typeof dir === 'undefined') { - dir = path.join(process.cwd(), 'templates'); - } - app.set('paths.templates'); -}; diff --git a/lib/transforms/env/travis.js b/lib/transforms/env/travis.js deleted file mode 100644 index 30128f99..00000000 --- a/lib/transforms/env/travis.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var stringify = require('stringify-travis-url'); - -/** - * Called in the `init` transform. Adds a `travis_url` - * property to the context. - */ - -module.exports = function(app) { - var github = app.get('data.github'); - if (! github) { - return; - } - var travis = stringify(github.user, github.repo); - app.set('data.travis_url', travis); -}; diff --git a/lib/transforms/env/user.js b/lib/transforms/env/user.js deleted file mode 100644 index c4848d11..00000000 --- a/lib/transforms/env/user.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Called in the `init` transform. Adds `user` and `username` - * for the current project to the context. - */ - -module.exports = function(app) { - app.set('data.username', app.get('data.github.username')); -}; diff --git a/lib/transforms/env/username.js b/lib/transforms/env/username.js deleted file mode 100644 index 8a28c5c6..00000000 --- a/lib/transforms/env/username.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var github = require('parse-github-url'); - -/** - * Called in the `username` transform, if the `git` transform - * was not able to find anything, this attempts to generate a - * username from other fields. - */ - -module.exports = function(app) { - if (!app.get('data.github.username')) { - var author = app.get('data.author'); - if (typeof author.url === 'string' && /\/github/.test(author.url)) { - var parsed = github(author.url); - var user = (parsed && parsed.user) || ''; - app.set('data.github.username', user); - app.set('data.username', user); - } - } -}; diff --git a/lib/transforms/init/argv.js b/lib/transforms/init/argv.js deleted file mode 100644 index 1bd03b9f..00000000 --- a/lib/transforms/init/argv.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * Prime the `verb.cache.argv` object. Used for setting values - * that are passed from the command line. - */ - -module.exports = function(app) { - app.cache.argv = app.cache.argv || {}; -}; diff --git a/lib/transforms/init/data.js b/lib/transforms/init/data.js deleted file mode 100644 index e177388a..00000000 --- a/lib/transforms/init/data.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('../../utils'); - -/** - * Prime `verb.cache.data` with empty package.json fields that - * will be over-written by the user's environment. - */ - -module.exports = function(app) { - app.data('../../pkg.json', function (fp) { - return utils.tryRequire(path.resolve(__dirname, fp)) || {}; - }); -}; diff --git a/lib/transforms/init/defaults.js b/lib/transforms/init/defaults.js deleted file mode 100644 index c930639c..00000000 --- a/lib/transforms/init/defaults.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var cwd = require('cwd'); - -/** - * Define default options. - */ - -module.exports = function(app) { - app.defaults('templates.examples', cwd('.')); - app.defaults('templates.includes', require('readme-includes')); - app.defaults('templates.badges', require('readme-badges')); - app.defaults('templates.docs', cwd('docs')); - - app.option({ - toc: {append: '\n\n_(Table of contents generated by [verb])_'} - }); - - // engines - app.option('view engine', 'md'); - - // routing - app.option('router methods'); - app.enable('case sensitive routing'); - app.enable('strict routing'); - - // delimiters - app.option('layoutDelims', ['<<%', '%>>']); - app.option('escapeDelims', ['{%%', '{%']); -}; diff --git a/lib/transforms/init/engines.js b/lib/transforms/init/engines.js deleted file mode 100644 index a0baccbe..00000000 --- a/lib/transforms/init/engines.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * Load built-in engines - */ - -module.exports = function(app) { - app.engine('md', require('engine-lodash'), { - delims: ['{%', '%}'] - }); -}; diff --git a/lib/transforms/init/helpers.js b/lib/transforms/init/helpers.js deleted file mode 100644 index ec2685e8..00000000 --- a/lib/transforms/init/helpers.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * Load default helpers - */ - -module.exports = function(app) { - require('../../helpers/sync')(app); - require('../../helpers/async')(app); - require('../../helpers/collections')(app); -}; diff --git a/lib/transforms/init/index.js b/lib/transforms/init/index.js deleted file mode 100644 index 874ea554..00000000 --- a/lib/transforms/init/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/lib/transforms/init/load.js b/lib/transforms/init/load.js deleted file mode 100644 index 70ae2728..00000000 --- a/lib/transforms/init/load.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var utils = require('../../utils'); -var path = require('path'); -var cwd = require('cwd'); - -/** - * Pre-load templates for built-in template types: - * | examples - * | includes - * | badges - * | docs - */ - -module.exports = function(app) { - app.badges('**/*.md', opts(app, 'badges', 'readme-badges')); - app.includes('**/*.md', opts(app, 'includes', 'readme-includes')); - app.examples('example.js', { cwd: cwd('.'), cache: true }); - app.docs('**/*.md', opts(app, 'docs')); -}; - -function opts(app, name) { - var paths = app.config.get('templates'); - var defaults = app.option('defaults.templates'); - var res = { cwd: process.cwd(), cache: true }; - - if (paths && paths.hasOwnProperty(name)) { - res.cwd = utils.tryRequire(paths[name]); - if (res.cwd) return res; - res.cwd = path.resolve(process.cwd(), paths[name]); - if (res.cwd) return res; - } - - if (defaults.hasOwnProperty(name)) { - res.cwd = utils.tryRequire(defaults[name]); - if (res.cwd) return res; - res.cwd = path.resolve(process.cwd(), defaults[name]); - } - return res; -} diff --git a/lib/transforms/init/loaders.js b/lib/transforms/init/loaders.js deleted file mode 100644 index 455e82f9..00000000 --- a/lib/transforms/init/loaders.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -var base = require('base-loader'); -var task = require('init-file-loader'); - -/** - * Load built-in loaders - */ - -module.exports = function(app) { - app.loader('base', [base]); - app.loader('task', [task]); -}; diff --git a/lib/transforms/init/metadata.js b/lib/transforms/init/metadata.js deleted file mode 100644 index 190579f7..00000000 --- a/lib/transforms/init/metadata.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var path = require('path'); - -/** - * Called in the `init` transform. Adds Verb's package.json - * data to `verb.metadata`. - * - * @param {Object} `app` - */ - -module.exports = function(app) { - Object.defineProperty(app, 'metadata', { - get: function () { - return require(path.resolve(__dirname, '../../..', 'package.json')); - }, - set: function () { - console.log('`verb.metadata` is read-only and cannot be modified.'); - } - }); -}; diff --git a/lib/transforms/init/middleware.js b/lib/transforms/init/middleware.js deleted file mode 100644 index 8f0cb5bb..00000000 --- a/lib/transforms/init/middleware.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -var middleware = require('../../middleware/'); -var mu = require('middleware-utils'); - -/** - * Initialize default middleware - */ - -module.exports = function(app) { - var delims = mu.delims(); - - app.onLoad(/./, mu.parallel([ - middleware.props, - middleware.engine, - middleware.src, - middleware.ext, - middleware.dest, - middleware.layout, - middleware.debug(app), - ]), mu.error('onLoad (js)')) - - .onLoad(/\.js$/, mu.parallel([ - middleware.copyright(app), - ]), mu.error('onLoad (js)')) - - .onLoad(/\.md$/, mu.series([ - middleware.copyright(app), - require('template-toc')(app), - middleware.examples(app), - delims.escape(), - ]), mu.error('onLoad (md)')) - - .preRender(/\.md$/, mu.parallel([ - middleware.readme(app), - ]), mu.error('preRender (md)')) - - .postRender(/\.md$/, mu.parallel([ - delims.unescape(), - middleware['lint-after'](app), - middleware.diff(app), - ]), mu.error('postRender')) -}; diff --git a/lib/transforms/init/plugins.js b/lib/transforms/init/plugins.js deleted file mode 100644 index 2821ecfd..00000000 --- a/lib/transforms/init/plugins.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -/** - * Load default settings - */ - -module.exports = function(app) { - // default `src` plugins - app.enable('src:vfs plugin'); - app.enable('src:init plugin'); - app.enable('src:conflicts plugin'); - app.enable('src:lint plugin'); - app.enable('src:drafts plugin'); - - // default `dest` plugins - app.enable('dest:todos plugin'); - app.enable('dest:paths plugin'); - app.enable('dest:render plugin'); - app.enable('dest:reflinks plugin'); - app.enable('dest:comments plugin'); - app.enable('dest:format plugin'); - app.enable('dest:vfs plugin'); -}; diff --git a/lib/transforms/init/runner.js b/lib/transforms/init/runner.js deleted file mode 100644 index dd33244e..00000000 --- a/lib/transforms/init/runner.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -/** - * Adds data to the context for the current `verb.runner`. - */ - -module.exports = function(app) { - app.data({ - runner: { - name: 'verb-cli', - url: 'https://github.com/assemble/verb-cli' - } - }); -}; diff --git a/lib/transforms/init/templates.js b/lib/transforms/init/templates.js deleted file mode 100644 index 84798667..00000000 --- a/lib/transforms/init/templates.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -/** - * Create built-in template types, using the `base` loader - */ - -module.exports = function(app) { - app.create('example', {isRenderable: true}, ['base']); - app.create('include', {isRenderable: true}, ['base']); - app.create('badge', {isRenderable: true}, ['base']); - app.create('doc', {isRenderable: true}, ['base']); -}; diff --git a/lib/transforms/modifiers/github_url.js b/lib/transforms/modifiers/github_url.js deleted file mode 100644 index 25b60865..00000000 --- a/lib/transforms/modifiers/github_url.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Modifier to add a github URl if a github username - * exists on the context. - */ - -module.exports = function github_(verb) { - var username = verb.get('data.github.username'); - if (username) { - verb.set('data.github.url', 'https://github/' + username); - } -}; diff --git a/lib/transforms/modifiers/index.js b/lib/transforms/modifiers/index.js deleted file mode 100644 index 874ea554..00000000 --- a/lib/transforms/modifiers/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('export-files')(__dirname); diff --git a/lib/transforms/modifiers/repository.js b/lib/transforms/modifiers/repository.js deleted file mode 100644 index f9128bc2..00000000 --- a/lib/transforms/modifiers/repository.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -/** - * Called in the `init` transform. Normalizes the - * `repository` field. - */ - -module.exports = function(app) { - var repo = app.get('data.repository'); - if (typeof repo === 'string') { - repo = 'git://github.com/' + repo + '.git'; - app.data({repository: {url: repo}}); - } -}; diff --git a/lib/transforms/modifiers/twitter_url.js b/lib/transforms/modifiers/twitter_url.js deleted file mode 100644 index 2a346a99..00000000 --- a/lib/transforms/modifiers/twitter_url.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Modifier to add a twitter URl if a twitter username - * exists on the context. - */ - -module.exports = function twitter_(verb) { - var username = verb.get('data.twitter.username'); - if (username) { - verb.set('data.twitter.url', 'http://twitter/' + username); - } -}; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 00000000..23a08c6a --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,170 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const util = require('util'); +const stores = {}; + +const defineProperty = (obj, name, fn) => { + Object.defineProperty(obj, name, { + configurable: true, + enumerable: true, + get() { + return fn(); + } + }); +}; + +defineProperty(exports, 'runner', () => require('base-runner')); +defineProperty(exports, 'middleware', () => require('common-middleware')); +defineProperty(exports, 'config-file', () => require('config-file')); +defineProperty(exports, 'DataStore', () => require('data-store')); +defineProperty(exports, 'extend', () => require('extend-shallow')); +defineProperty(exports, 'exists', () => require('fs-exists-sync')); +defineProperty(exports, 'generate', () => require('generate')); +defineProperty(exports, 'get', () => require('get-value')); +defineProperty(exports, 'gm', () => require('global-modules')); +defineProperty(exports, 'format', () => require('gulp-format-md')); +defineProperty(exports, 'reflinks', () => require('gulp-reflinks')); +defineProperty(exports, 'typeOf', () => require('kind-of')); +defineProperty(exports, 'log', () => require('log-utils')); +defineProperty(exports, 'MacroStore', () => require('macro-store')); +defineProperty(exports, 'merge', () => require('merge-deep')); +defineProperty(exports, 'pick', () => require('object.pick')); +defineProperty(exports, 'set', () => require('set-value')); +defineProperty(exports, 'strip', () => require('strip-color')); +defineProperty(exports, 'table', () => require('text-table')); +defineProperty(exports, 'through', () => require('through2')); +defineProperty(exports, 'parse', () => require('yargs-parser')); + +/** + * Format a value to be displayed in the command line + */ + +exports.formatValue = val => exports.cyan(util.inspect(val, null, 10)); + +/** + * Return true if a value is an object. + */ + +exports.isObject = val => exports.typeOf(val) === 'object'; + +/** + * Returns true if `val` is true or is an object with `show: true` + * + * @param {String} `val` + * @return {Boolean} + */ + +exports.show = val => exports.isObject(val) && val.show === true; + +/** + * Initialize stores + */ + +exports.stores = proto => { + // Create `macros` store + Object.defineProperty(proto, 'macros', { + configurable: true, + set(val) { + stores.macros = val; + }, + get() { + return stores.macros || (stores.macros = new exports.MacroStore({ name: 'verb-macros' })); + } + }); + + // Create `app.globals` store + Object.defineProperty(proto, 'globals', { + configurable: true, + set(val) { + stores.globals = val; + }, + get() { + return stores.globals || (stores.globals = new exports.DataStore('verb-globals')); + } + }); +}; + +/** + * Argv options + */ + +exports.opts = { + boolean: ['diff'], + alias: { + add: 'a', + config: 'c', + configfile: 'f', + diff: 'diffOnly', + global: 'g', + help: 'h', + silent: 'S', + verbose: 'v', + version: 'V', + remove: 'r' + } +}; + +exports.parseArgs = argv => { + const obj = exports.parse(argv, exports.opts); + if (obj.init) { + obj._.push('init'); + delete obj.init; + } + if (obj.help) { + obj._.push('help'); + delete obj.help; + } + return obj; +}; + +exports.getConfig = (app, name) => { + const runtimeConfig = path.resolve(app.cwd, name); + let config = exports.configFile('.verbrc.json') || {}; + + if (exports.exists(runtimeConfig)) { + const rc = JSON.parse(fs.readFileSync(runtimeConfig, 'utf8')); + config = exports.extend({}, config, rc); + app.base.set('cache.config', config); + } +}; + +exports.getTasks = (configFile, arrays) => { + arrays = exports.arrayify(arrays); + let tasks = []; + + if (configFile) { + tasks = exports.arrayify(arrays[0]); + return tasks.length >= 1 ? tasks : ['default']; + } + + for (let i = 0; i < arrays.length; i++) { + const arr = exports.arrayify(arrays[i]); + // If `default` task is defined, continue + if (arr.length === 1 && arr[0] === 'default') { + continue; + } + // If nothing is defined, continue + if (arr.length === 0) { + continue; + } + tasks = arr; + break; + } + return tasks; +}; + +exports.arrayify = val => val ? Array.isArray(val) ? val : [val] : []; + +exports.firstIndex = (arr, items) => { + items = exports.arrayify(items); + let idx = -1; + for (let i = 0; i < arr.length; i++) { + if (items.indexOf(arr[i]) !== -1) { + idx = i; + break; + } + } + return idx; +}; diff --git a/lib/utils/defaultIgnores.js b/lib/utils/defaultIgnores.js deleted file mode 100644 index 65516abd..00000000 --- a/lib/utils/defaultIgnores.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -module.exports = [ - '*.DS_Store', - '.git', - 'bower_components', - 'node_modules', - 'npm-debug.log', - 'actual', - 'fixtures', - 'test/actual', - 'test/fixtures', - 'temp', - 'tmp', - 'vendor', - 'wip' -]; diff --git a/lib/utils/index.js b/lib/utils/index.js deleted file mode 100644 index cb1cc7f8..00000000 --- a/lib/utils/index.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; - -/** - * Module dependencies - */ - -var path = require('path'); -var mdu = require('markdown-utils'); -var mm = require('micromatch'); - -/** - * Expose `utils` - */ - -var utils = require('export-files')(__dirname); - -/** - * File cache - */ - -var cache = {}; - -/** - * Create a url from the given `domain`, optionally pass `true` - * as the second argument to use `https` as the protocol. - * - * @param {String} `domain` - * @param {String} `https` - * @return {String} - */ - -utils.linkify = function linkify(domain, https) { - return function (author) { - if (typeof author !== 'object') { - throw new TypeError('utils.linkify expects author to be an object.'); - } - - var username = author.username; - if (typeof username === 'undefined') { - username = this.context[domain] && this.context[domain].username; - } - - if (typeof username === 'undefined') { - username = this.config.get(domain + '.username'); - } - - if (typeof username === 'object') { - var o = username; - username = o.username; - } - - if (!username) return ''; - - var protocol = https ? 'https://' : 'http://'; - var res = mdu.link(domain + '/' + username, protocol + domain + '.com/' + username); - return '+ ' + res; - }; -}; - -/** - * Returns a matching function to use against - * the list of given files. - * - * @param {Array} `files` The list of files to match against. - * @return {Array} Array of matching files - */ - -utils.files = function files(arr) { - return function(pattern, options) { - return mm(arr, pattern, options); - }; -}; - -/** - * Try to require a file, fail silently. Encapsulating try-catches - * also helps with v8 optimizations. - */ - -utils.tryRequire = function tryRequire(fp) { - if (typeof fp !== 'string') { - throw new Error('utils.tryRequire() expects a string.'); - } - var key = 'tryRequire:' + fp; - if (cache.hasOwnProperty(key)) return cache[key]; - try { - return (cache[key] = require(fp)); - } catch(err) {} - try { - return (cache[key] = require(path.resolve(fp))); - } catch(err) {} - return null; -}; - -/** - * Get the basename of a file path, excluding extension. - * - * @param {String} `fp` - * @param {String} `ext` Optionally pass the extension. - */ - -utils.basename = function basename(fp, ext) { - if (typeof fp === 'undefined') { - throw new Error('utils.basename() expects a string.'); - } - return fp.substr(0, fp.length - (ext || path.extname(fp)).length); -}; - -/** - * Ensure that a file extension is formatted properly. - * - * @param {String} `ext` - */ - -utils.formatExt = function formatExt(ext) { - // could be filepath with no ext, e.g. `LICENSE` - if (typeof ext === 'undefined') return; - if (Array.isArray(ext)) return ext.map(utils.formatExt); - if (ext.charAt(0) !== '.') ext = '.' + ext; - return ext; -}; - -/** - * Cast `val` to an array. - */ - -utils.arrayify = function arrayify(val) { - var isArray = Array.isArray(val); - if (typeof val !== 'string' && !isArray) { - throw new Error('utils.arrayify() expects a string or array.'); - } - return isArray ? val : [val]; -}; - -/** - * Detect if the user has specfied not to render a file. - */ - -utils.norender = function norender(verb, file, locals) { - var ext = file && file.ext ? utils.formatExt(ext) : null; - if (ext && !verb.engines.hasOwnProperty(ext)) { - return false; - } - - return verb.isTrue('norender') || verb.isFalse('render') - || file.norender === true || file.render === false - || locals.norender === true || locals.render === false; -}; - -/** - * Expose `utils` - */ - -module.exports = utils; diff --git a/package.json b/package.json index 79bb7d5d..456d7714 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,26 @@ { "name": "verb", - "description": "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.", - "version": "0.8.8", + "description": "Documentation build system for GitHub projects, powered by node.js. Verb has full support for gulp and assemble plugins and can be used to create documentation generators, themes, documentation websites and much more!", + "version": "0.9.0", "homepage": "https://github.com/verbose/verb", - "author": { - "name": "Jon Schlinkert", - "url": "https://github.com/jonschlinkert" - }, - "repository": { - "type": "git", - "url": "git://github.com/verbose/verb.git" - }, + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "verbose/verb", "bugs": { "url": "https://github.com/verbose/verb/issues" }, "license": "MIT", "files": [ + "bin", "index.js", - "lib/" + "lib", + "LICENSE", + "README.md" ], "main": "index.js", + "preferGlobal": true, + "bin": { + "verb": "bin/verb.js" + }, "engines": { "node": ">=0.10.0" }, @@ -27,136 +28,119 @@ "test": "mocha" }, "dependencies": { - "ansi-bold": "^0.1.1", - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "ansi-yellow": "^0.1.1", - "arr-union": "^3.0.0", - "async-helper-base": "^0.2.0", - "base-loader": "^0.1.0", - "chalk": "^1.1.1", - "composer": "^0.3.0", - "computed-property": "^0.1.2", - "cwd": "^0.8.0", - "data-store": "^0.8.2", - "diff": "^2.0.2", - "engine-lodash": "^0.8.0", - "event-stream": "^3.3.1", - "export-files": "^2.1.0", + "ansi-colors": "^4.1.3", + "base-runner": "^0.8.2", + "common-middleware": "^0.3.0", + "config-file": "^0.3.2", + "data-store": "^4.0.3", + "debug": "^2.2.0", + "diff": "^2.2.3", + "engine-base": "^0.1.2", + "export-files": "^2.1.1", "extend-shallow": "^2.0.1", - "get-value": "^1.1.5", - "gfm-code-blocks": "^0.3.0", - "git-user-name": "^1.1.2", - "globby": "^2.1.0", - "gulp-drafts": "^0.2.0", - "helper-changelog": "^0.1.0", - "helper-codelinks": "^0.1.2", - "helper-copyright": "^1.4.0", - "helper-coverage": "^0.1.2", - "helper-date": "^0.2.2", - "helper-license": "^0.2.1", - "helper-reflinks": "^1.4.1", - "helper-related": "^0.10.0", - "helper-resolve": "^0.3.1", - "helper-toc": "^0.2.0", - "init-file-loader": "^0.1.1", - "is-plain-object": "^2.0.1", - "is-true": "^0.1.0", - "js-comments": "^0.5.4", - "lint-templates": "^0.1.2", - "lodash": "^3.10.1", - "log-symbols": "^1.0.2", - "logging-helpers": "^0.4.0", - "markdown-toc": "^0.11.5", - "markdown-utils": "^0.7.1", - "micromatch": "^2.2.0", - "middleware-utils": "^0.1.4", - "parse-author": "^0.2.0", - "parse-copyright": "^0.4.0", - "parse-filepath": "^0.6.3", - "parse-github-url": "^0.1.1", - "parse-gitignore": "^0.2.0", - "parser-front-matter": "^1.2.5", - "plugin-error": "^0.1.2", - "pretty-remarkable": "^0.1.8", - "readme-badges": "^0.3.3", - "readme-includes": "^0.2.9", - "relative": "^3.0.1", - "relative-dest": "^0.1.0", - "remarkable": "^1.6.0", - "stringify-travis-url": "^0.1.0", - "template": "^0.14.3", - "template-helper-apidocs": "^0.4.4", - "template-helpers": "^0.3.2", - "template-toc": "^0.3.3", - "through2": "^2.0.0", - "update-copyright": "^0.1.0", - "vinyl-fs": "^1.0.0", - "year": "^0.2.1" + "fs-exists-sync": "^0.1.0", + "generate": "^0.9.8", + "get-value": "^2.0.6", + "global-modules": "^0.2.3", + "gulp-format-md": "^2.0.0", + "gulp-reflinks": "^0.2.0", + "kind-of": "^3.0.4", + "lazy-cache": "^2.0.1", + "log-utils": "^0.2.1", + "macro-store": "^0.1.0", + "merge-deep": "^3.0.0", + "mixin-deep": "^2.0.1", + "object.pick": "^1.1.2", + "set-blocking": "^2.0.0", + "set-value": "^4.1.0", + "strip-color": "^0.1.0", + "text-table": "^0.2.0", + "through2": "^2.0.1", + "yargs-parser": "^2.4.1" }, "devDependencies": { - "consolidate": "^0.13.1", - "gulp-istanbul": "^0.10.0", - "gulp-jshint": "^1.11.2", - "gulp-mocha": "^2.1.3", - "gulp-util": "^3.0.6", - "handlebars": "^3.0.3", - "jshint-stylish": "^2.0.1", - "mocha": "*", - "should": "*", - "swig": "^1.4.2" + "base-config-process": "^0.1.9", + "base-generators": "^0.4.5", + "base-questions": "^0.7.3", + "base-store": "^0.4.4", + "base-test-runner": "^0.2.0", + "base-test-suite": "^0.2.3", + "cross-spawn": "^4.0.0", + "eslint": "^8.45.0", + "generate-foo": "^0.1.5", + "generator-util": "^0.2.9", + "graceful-fs": "^4.1.5", + "gulp": "^4.0.2", + "gulp-eslint": "^3.0.1", + "gulp-istanbul": "^1.0.0", + "gulp-mocha": "^2.2.0", + "gulp-unused": "^0.1.2", + "is-absolute": "^0.2.5", + "load-pkg": "^3.0.1", + "mocha": "^2.5.3", + "npm-install-global": "^0.1.2", + "resolve": "^1.1.7", + "should": "^10.0.0", + "sinon": "^1.17.5", + "verb-generate-readme": "^0.1.28" }, "keywords": [ - "comment", - "comments", - "doc", - "docs", - "document", - "documentation", - "generate", - "generator", - "gh", - "gh-pages", - "markdown", - "md", - "pages", - "readme", - "repo", - "repository", - "template", - "templates", - "verb", - "verbiage" + "verb" ], - "verb": { + "lintDeps": { "ignore": [ - ".git" - ], - "deps": { - "include": [ - "readme-includes", - "readme-badges" - ], - "ignore": [ - "support" - ] + "coverage", + "docs", + "lib/templates" + ] + }, + "verb": { + "toc": { + "render": true, + "method": "preWrite", + "maxdepth": 3 }, + "layout": "app", + "tasks": [ + "readme" + ], + "plugins": [ + "gulp-format-md" + ], "related": { "list": [ - "composer", "assemble", - "template", - "engine" + "base", + "generate", + "update" ] }, - "reflinks": { - "list": [ - "jsdiff", - "option-cache", - "template", - "plasma", - "config-cache" - ] + "reflinks": [ + "assemble", + "base", + "consolidate", + "gulp", + "handlebars", + "lodash", + "nunjucks", + "update" + ], + "sections": false, + "lint": { + "reflinks": true + } + }, + "generator": { + "toc": true, + "layout": "default", + "tasks": [ + "readme" + ], + "plugins": [ + "gulp-format-md" + ], + "lint": { + "reflinks": true } } } diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..6b4a5cc5 --- /dev/null +++ b/readme.md @@ -0,0 +1,410 @@ +

+ + + + +

+ +Documentation build system for GitHub projects, powered by node.js. Verb has full support for gulp and assemble plugins and can be used to create documentation generators, themes, documentation websites and much more! + +# verb + +[![NPM version](https://img.shields.io/npm/v/verb.svg?style=flat)](https://www.npmjs.com/package/verb) [![NPM monthly downloads](https://img.shields.io/npm/dm/verb.svg?style=flat)](https://npmjs.org/package/verb) + +A project without documentation is like a project that doesn't exist. Verb solves this by making it dead simple to generate documentation, using simple markdown templates, with zero configuration required. + +## What is verb? + +**Documentation system** + +Verb is a documentation system that is simple and fast enough to use for [generating a readme](https://github.com/verbose/verb-generate-readme) for a GitHub project, yet powerful and smart enough to build the most complex documentation projects around. + +**I just want to generate a readme, can verb do this?** + +Yes! See [verb-generate-readme](https://github.com/verbose/verb-generate-readme). + +**Highly pluggable** + +Verb is also highly pluggable, with first-class plugin support. Moreover, verb also supports [gulp](https://gulpjs.com), [base](https://github.com/node-base/base), [assemble](https://github.com/assemble/assemble), [generate](https://github.com/generate/generate) and [update](https://github.com/update/update) plugins. + +### Why use verb? + +**Why should I use verb, instead of X?** + +Verb is not a _documentation generator_, it's a documentation system that can be used to create documentation generators like [documentation.js](https://github.com/documentationjs/documentation) and [slate](https://github.com/lord/slate). + +For example, [verb-generate-readme](https://github.com/verbose/verb-generate-readme) is a sophisticated readme generator that renders templates using data from the user's environment, like package.json and git config. For API documentation, verb-readme-generator uses template helpers. + +## Highlights + +**Templating** + +* Render templates with any node.js engine, including [handlebars](https://www.handlebarsjs.com/), [lodash](https://lodash.com/), [nunjucks](https://github.com/mozilla/nunjucks), and all [consolidate](https://github.com/ladjs/consolidate) engines. +* Convert markdown to HTML, or render markdown templates back to markdown (see [.verb.md](#verbmd)) +* Template collections +* Support for "pages", "partials", and "layouts" +* Helper support (sync and async!) +* Middleware that can be used at any point in the render cycle + +**Helpers** + +* Generate a list of [related links](https://github.com/helpers/helper-related) to other npm packages +* Resolve [reflinks](https://github.com/helpers/helper-reflinks) +* More than 150 other helpers available from [template-helpers](https://github.com/jonschlinkert/template-helpers) and the [helpers org](https://github.com/helpers) + +**Plugins** + +Verb has rich plugin support, along with native support for [gulp plugins][], so if you want to publish a documentation site, you can do things like: + +* Ignore files marked as "drafts" +* CSS minification or reduction +* SASS or LESS compiling and minification +* JavaScript minification, reduction or concatenation +* Asset copying or renaming +* Image compression +* HTML minification and linting +* RSS feeds +* Anything else you can do with [gulp plugins][] + +**Generators** + +Verb supports [generate](https://github.com/generate/generate) generators, which are plugins that are registered by name and can be run by command line or API. Generators can also be published to npm and installed globally or locally. + +See the [generate](https://github.com/generate/generate) project and [documentation](https://github.com/generate/generatedocs) for more details. + +## Table of Contents + +* [Getting started](#getting-started) + - [Installing verb](#installing-verb) + - [init](#init) + - [Generators](#generators) +* [verbfile.js](#verbfilejs) +* [Config](#config) +* [Command line](#command-line) + - [Built-in tasks](#built-in-tasks) + - [Config flags](#config-flags) + - [Config examples](#config-examples) + - [Other flags](#other-flags) +* [Upgrading](#upgrading) +* [About](#about) + - [Related projects](#related-projects) + - [Community](#community) + - [Contributing](#contributing) + - [Running tests](#running-tests) + - [Author](#author) + - [License](#license) + +_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ + +## Getting started + +### Installing verb + +To use verb's CLI, you will first need to install it globally. You can do that now with the following command: + +```sh +$ npm --global install verb +``` + +This adds the `verb` command to your system path, allowing it to be run from any directory. + +### init + +If you want to test that verb works, while also initializing settings for the current project, run: + +```sh +$ verb init +``` + +### Generators + +All of the "real work" in verb is accomplished using generators. Now that `verb` is installed, you can run any [generator found on npm](https://www.npmjs.com/search?q=generategenerator) + +**Install readme generator** + +The `verb-generate-readme` is a good example of what's possible with verb, and it's an easy way to get started. You can install the library with the following command: + +```sh +$ npm i -g verb-generate-readme +``` + +**.verb.md** + +You should now be able to generate your project's readme from a `.verb.md` template with the following command: + +```sh +$ verb readme +``` + +Or you can go a step further by adding a `verbfile.js` to your project, allowing you to customize verb with generators, plugins, middleware, helpers, templates and more! + +**verbfile.js** + +Next, create a `verbfile.js` with the following code: + +```js +module.exports = function(app) { + app.extendWith('verb-generate-readme'); + + // call the `readme` task from verb-generate-readme + app.task('default', ['readme']); +}; +``` + +## verbfile.js + +Write any custom code in your project's `verbfile.js` (like `gulpfile.js` or `Gruntfile.js`). + +**Heads up!** + +_If you're using Verb's CLI, `verbfile.js` should export an instance of Verb or a function._ + +**Function** + +When a function is exported, we refer to these as [verb generators](#generators). Generators can be locally or globally installed, and can consist of entirely custom code, or created using other generators and plugins like building blocks. + +```js +module.exports = function(app) { + // "app" is an instance of verb created by Verb's CLI for this file +}; +``` + +**Instance** + +```js +var verb = require('verb'); +var app = verb(); + +module.exports = app; +``` + +## Config + +Options can be defined in `package.json` on the `verb` object. + +**Example** + +Specify special data to be used in templates for a project: + +```json +{ + "name": "my-project", + "verb": { + "data": { + "twitter": "jonschlinkert" + } + } +} +``` + +See the [config docs](docs/config.md) for more details. + +## Command line + +### Built-in tasks + +Verb only has a few built-in [tasks](docs/tasks.md), but these will externalized to [generators](docs/generators.md) in the near future. + +#### list + +List currently installed verb [generators](docs/generators.md). + +```js +$ verb new +``` + +#### new + +Generate a new [verbfile.js](docs/verbfile.js) in the current working directory. + +```js +$ verb new +``` + +### Config flags + +There are a few flags dedicated to setting and persisting configuration settings. + +**Flag** | **Type** | **Scope** | **Description** | **Location** + +--- | --- | --- +`--option` | In-memory | Global | Set an option to use in the current session | N/A +`--data` | In-memory | Global | Add data to the context for rendering templates in the current session | N/A +`--config` | Persisted | Project | Persist a configuration setting _to use on the current project_ every time `verb` is run | `~/verb-globals.json` +`--set` | Persisted | Persist a configuration setting _to use on every project_ every time `verb` is run | The `verb` object in package.json + +### Config examples + +#### Settings + +Persist configuration settings to the `verb` object in `package.json`. + +```sh +$ verb --config= +``` + +**Related links** + +Persist a list of project names to generate [related links](https://github.com/helpers/helper-related): + +```sh +$ verb --config=related.list:generate,assemble,update +``` + +Updates the `verb` object in package.json with the following: + +```json +{ + "verb": { + "related": { + "list": [ + "generate", + "assemble", + "update" + ] + } + } +} +``` + +#### Tasks + +Specify tasks to run on a project: + +```sh +$ verb --config=tasks: +``` + +**Example** + +Automatically run [verb-generate-readme](https://github.com/verbose/verb-generate-readme) on a project: + +```sh +$ verb --config=tasks:readme +# or, specify an array of tasks +$ verb --config=tasks:foo,bar,baz +``` + +Updates the `verb` object in package.json with the following: + +```json +{ + "verb": { + "tasks": ["readme"] + } +} +``` + +### Other flags + +#### --init + +Initialize a `verb` config object in package.json of the current project. + +```sh +$ verb --init +``` + +#### --save + +Persist options values to the global data store for `app` ([verb](https://github.com/verbose/verb), [assemble](https://github.com/assemble/assemble), [generate](https://github.com/generate/generate), [update](https://github.com/update/update), etc) + +```sh +$ verb --save +``` + +Most of the above CLI commands can be prefixed with `--save` to persist the value to the global config store. + +#### --file + +Specify the file to use instead of `verbfile.js`. + +```sh +$ verb --file +``` + +**Example** + +```sh +$ verb --file foo.js +``` + +#### --cwd + +Specify the cwd to use + +```sh +$ verb --cwd= +``` + +**Example** + +```sh +$ verb --cwd="foo/bar" +``` + +Display the currently defined cwd: + +```sh +$ verb --cwd +``` + +## Upgrading + +**Clear your cache and re-install** + +If you're currently running verb v0.8.0 or lower, please do the following to clear out old versions of verb, so that the latest version of verb will install properly: + +```bash +$ npm cache clean && npm i -g verb +``` + +## About + +### Related projects + +* [assemble](https://www.npmjs.com/package/assemble): Get the rocks out of your socks! Assemble makes you fast at creating web projects… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Get the rocks out of your socks! Assemble makes you fast at creating web projects. Assemble is used by thousands of projects for rapid prototyping, creating themes, scaffolds, boilerplates, e-books, UI components, API documentation, blogs, building websit") +* [base](https://www.npmjs.com/package/base): Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks | [homepage](https://github.com/node-base/base "Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks") +* [generate](https://www.npmjs.com/package/generate): Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the robustness and configurability of Yeoman, the expressiveness and simplicity of Slush, and more powerful flow control and composability than either.") +* [update](https://www.npmjs.com/package/update): Be scalable! Update is a new, open source developer framework and CLI for automating updates… [more](https://github.com/update/update) | [homepage](https://github.com/update/update "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.") + +### Community + +Bigger community means more plugins, better support and more progress. Help us make Generate better by spreading the word: + +* Show your love by starring the project +* Tweet about Generate. Mention using `@generatejs`, or use the `#generatejs` hashtag +* Get implementation help on [StackOverflow](http://stackoverflow.com/questions/tagged/generate) with the `generatejs` tag +* Discuss Generate with us on [Gitter](https://gitter.im/generate/generate) +* If you publish a generator, to make your project as discoverable as possible, please add the unique keyword `generategenerator` to your project's package.json. + +### Contributing + +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). + +Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards. + +### Running tests + +Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: + +```sh +$ npm install && npm test +``` + +### Author + +**Jon Schlinkert** + +* [GitHub Profile](https://github.com/jonschlinkert) +* [Twitter Profile](https://twitter.com/jonschlinkert) +* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert) + +### License + +Copyright © 2024, [Jon Schlinkert](https://github.com/jonschlinkert). +Released under the [MIT License](LICENSE). + +*** + +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on July 10, 2024._ \ No newline at end of file diff --git a/recipes/api-docs.md b/recipes/api-docs.md deleted file mode 100644 index 045f73b0..00000000 --- a/recipes/api-docs.md +++ /dev/null @@ -1,13 +0,0 @@ - -```js -var verb = require('verb'); -var tap = require('gulp-tap'); - -verb.task('api', function () { - verb.src('api/**/*.md') - // .pipe(tap(function (file) { - // console.log('file', file.contents.toString()); - // })) - .pipe(verb.dest('api-dest')); -}); -``` \ No newline at end of file diff --git a/recipes/api-documentation.md b/recipes/api-documentation.md deleted file mode 100644 index dd699eaf..00000000 --- a/recipes/api-documentation.md +++ /dev/null @@ -1,17 +0,0 @@ -# API docs - -> Writing and maintaining API documentation with Verb - - -```js -var verb = require('verb'); -var tap = require('gulp-tap'); - -verb.task('api', function () { - verb.src('api/**/*.md') - // .pipe(tap(function (file) { - // console.log('file', file.contents.toString()); - // })) - .pipe(verb.dest('api-dest')); -}); -``` diff --git a/recipes/generate-a-table-of-contents.md b/recipes/generate-a-table-of-contents.md deleted file mode 100644 index da3ae28c..00000000 --- a/recipes/generate-a-table-of-contents.md +++ /dev/null @@ -1,25 +0,0 @@ -# Generate a Table of Contents - -> Single or multi-file! - -For a single-file TOC, add the following to the place where you want the TOC to render: - -```html - -``` - -For a multi-file TOC, add the following to the place where you want the TOC to render: - -```html - -``` - -**Escaping** - -If you ever need to add a TOC comment to documentation for illustrative purposes and you do not want it to render: - -Add double exclamation points, like this: - -```html - -``` diff --git a/recipes/template-data.md b/recipes/template-data.md deleted file mode 100644 index 0b92a62c..00000000 --- a/recipes/template-data.md +++ /dev/null @@ -1,38 +0,0 @@ -## Template data - -> Load data to pass to templates - - -```js -var verb = require('verb'); -``` - -```js -verb.data('data/*.yml'); -verb.layout('default.md', '\n<<% body %>>\n'); -verb.includes('includes/*.md'); -verb.docs('docs/*.md'); - -// verb.helper('comments', require('verb-helper-comments')); - -verb.helperAsync('docs', function (name, locals, cb) { - var doc = this.cache.docs[name]; - this.render(doc, locals, function (err, content) { - if (err) return cb(err); - cb(null, content); - }); -}); - -verb.task('foo', function() { - verb.src('.verbrc.md') - .pipe(verb.dest('temp')); -}); - -verb.task('readme', function() { - verb.src('.verbrc.md') - // .pipe(dest(':dest/:basename')) - .pipe(verb.dest('./')); -}); - -verb.task('default', ['foo', 'readme']); -``` \ No newline at end of file diff --git a/recipes/using-layouts.md b/recipes/using-layouts.md deleted file mode 100644 index 4154e9b9..00000000 --- a/recipes/using-layouts.md +++ /dev/null @@ -1,21 +0,0 @@ -## Using layouts - -> Layouts are used to wrap other templates with common or project-wide content. - -In your verbfile.js: - -```js -var verb = require('verb'); - -// register a layout with verb. -verb.layout('default.md', {content: '# {%= name %}\n\n<<% body %>>\n'}); - -// register a page, and use the layout by adding the `layout` property. -verb.page('api.md', {content: 'API docs...', layout: 'default.md'}); - -//=> '# {%= name %}\n\nAPI docs...\n' -``` - -**What's with the `body` syntax** - -Verb is a documentation generator, the crazy `body` syntax is verb's way of ensure that we avoid collision with templates that might be in code examples. \ No newline at end of file diff --git a/recipes/verbfile.md b/recipes/verbfile.md deleted file mode 100644 index fd241be6..00000000 --- a/recipes/verbfile.md +++ /dev/null @@ -1,15 +0,0 @@ -# verbfile - -> Creating a basic verbfile.js - -This is what the default `verbfile.js` looks like: - -```js -var verb = require('verb'); - -verb.task('default', function() { - verb.src('.verb.md') - .pipe(verb.dest('.')); -}); -``` - diff --git a/recipes/verbmd.md b/recipes/verbmd.md deleted file mode 100644 index 6772d44b..00000000 --- a/recipes/verbmd.md +++ /dev/null @@ -1,94 +0,0 @@ -# .verb.md - -> .verb.md is a markdown template that Verb uses to generate your project's readme. - -If your project has a `.verb.md` template, verb will automatically render it using data from your project's package.json file. - -## Example .verb.md - -You can use any variables, helpers, templates or data you want in Verb documentation. This example below just happens to be the template that Verb's maintainers like on their own projects. - -Below we'll describe each section. - -```markdown -# {%%= name %} {%%= badge("fury") %} - -> {%%= description %} - -## Install -{%%= include("install") %} - -## API -{%%= apidocs("index.js") %} - -## Author -{%%= include("author") %} - -## License -{%%= copyright() %} -{%%= license() %} - -*** - -{%%= include("footer") %} -``` - -_(before going further, please read [how verb works](./how-verb-works.md) if you need a primer on how these templates work.)_ - -There are only a couple of template variables used, the rest of example consists of [helpers](./helpers.md). Let's go over all of them. - - -## Variables - -Simple variables, like `name` are typically used when their values can be resolved using data from package.json without doing a lot of work to get the data. - -### name - -Uses the value of the `name` property from package.json. - -```js -{%%= name %} -``` - -Uses the `description` property from package.json. - -```js -> {%%= description %} -``` - -## Helpers - -Helpers are used when data needs to be updated dynamically, or when it needs to be calculated - -### badge - -This is a helper that gets badge templates from node_modules and renders them using data in package.json. There are other badges, like `travis`, but you can add your own if you want or do a pr to verb to add more. - -```js -{%%= badge("fury") %} -``` - -### description - -```js -{%%= include("install") %} -``` - -```js -{%%= apidocs("index.js") %} -``` - -```js -{%%= include("author") %} -``` - -```js -{%%= license() %} -``` -```js -{%%= copyright() %} -``` - -```js -{%%= include("footer") %} -``` \ No newline at end of file diff --git a/support/logo.png b/support/logo.png new file mode 100755 index 00000000..011b46ab Binary files /dev/null and b/support/logo.png differ diff --git a/support/templates/config.md b/support/templates/config.md new file mode 100644 index 00000000..562e1b8e --- /dev/null +++ b/support/templates/config.md @@ -0,0 +1,168 @@ +--- +title: Verb config +--- + +The `verb` object in package.json may be used to define configuration settings for the current project. + +## Overriding values + +Most of the configuration options defined in package.json will be used as "project-wide" settings. + +For example, the `layout` defined in package.json will be applied to all templates in the project. This is not ideal in many cases, so verb gives you several ways to override these settings in cases where you need to be more granular. + +- **[front-matter](front-matter.md)**: For most options, values defined in the [front matter](front-matter.md) of individual templats will override the values defined in package.json. +- **[middleware](middleware.md)**: +- **[plugin](pipeline-plugins.md)**: + +## Supported properties + +- [layout](#layout) +- [lint](#lint) +- [middleware](#middleware) +- [plugins](#plugins) +- [reflinks](#reflinks) +- [related](#related) +- [run](#run) +- [tasks](#tasks) +- [toc](#toc) + + +### layout + +Define the "default" [layout](layouts.md) to use for all templates in the project. + +**Type**: `String` + +**Default**: `undefined` + +**Example** + +```js +{ + "layout": "default" +} +``` + +### lint + +**Type**: `Boolean` + +**Default**: `undefined` + +**Example** + +```js +{ + "lint": true +} +``` + +### middleware + +**Type**: `Object` + +**Default**: `undefined` + +**Example** + +```js +{ + "middleware": {} +} +``` + +### plugins + +**Type**: `Object` + +**Default**: `undefined` + +**Example** + +```js +{ + "plugins": {} +} +``` + +### reflinks + +**Type**: `Object` + +**Default**: `undefined` + +**Example** + +```js +{ + "reflinks": {} +} +``` + +### related + +**Type**: `Object` + +**Default**: `undefined` + +**Example** + +```js +{ + "related": {} +} +``` + +### run + +**Type**: `Boolean` + +**Default**: `undefined` + +**Example** + +```js +{ + "run": {} +} +``` + +### tasks + +**Type**: `Array` + +**Default**: `undefined` + +**Example** + +```js +{ + "tasks": {} +} +``` + +### toc + +**Type**: `Boolean|Object` + +**Default**: `undefined` + +**Example** + +```js +{ + // converted to "{render: true}" + "toc": true +} +// or +{ + "toc": { + "render": true, + "method": "postRender" + } +} +``` + +**Options** + +- `render`: actually add the Table of Contents to the file. \ No newline at end of file diff --git a/support/templates/features.md b/support/templates/features.md new file mode 100644 index 00000000..04624c98 --- /dev/null +++ b/support/templates/features.md @@ -0,0 +1,41 @@ +--- +title: Features +--- + +* Create your own documentation generator +* Render templates +* [Generate a README.md][verb-readme-generator] from a template + ++ **Tasks** - run [gulp][]-like tasks. Verb's task system is powered by [bach][], the same library used in [gulp][] v4.0. ++ **Generators** - powerful flow control, with support for [generate][] generators (verb is built on top of generate) ++ **"Smart" plugins** - highly pluggable, with 75+ [smart plugins]() available for creating custom verb applications. Additionaly, most [base][], [generate][], [assemble][], and [update][] plugins can be used with verb. ++ **Streams** - methods for working with the file system and streams, with full support for [assemble][] and [gulp][] plugins ++ **Markdown** - Create markdown documentation from templates ++ **Templates** - Render templates using any template engine, such as [handlebars][], [lodash][] or [swig][]. Or any engine supported by [consolidate][]. ++ **Layouts and partials** - Support for layouts, partials, and custom template collections ++ **Collections** - First class template collection support, powered by the [templates][] library ++ **Helpers** - Async and sync helper support. Use any helpers from [template-helpers][], [handlebars-helpers][], or any helper from the [helpers org](https://github.com/helpers). + +## Bonus features + +### Plugin ecosystem + +Verb is build on [base][] and shares a common plugin ecosystem with the following applications: + +- [generate][]: generat projects +- [assemble][]: build projects +- [update][]: maintain projects + +### Easy to extend + +TODO + +```js +module.exports = function(verb) { + verb.use(require('verb-readme-generator')); + + verb.task('default', ['readme'], function(cb) { + cb(); + }); +}; +``` diff --git a/support/templates/front-matter.md b/support/templates/front-matter.md new file mode 100644 index 00000000..84b173eb --- /dev/null +++ b/support/templates/front-matter.md @@ -0,0 +1,5 @@ +--- +title: Front-matter +--- + +TODO \ No newline at end of file diff --git a/support/templates/layouts.md b/support/templates/layouts.md new file mode 100644 index 00000000..4b88e80f --- /dev/null +++ b/support/templates/layouts.md @@ -0,0 +1,5 @@ +--- +title: Layouts +--- + +TODO \ No newline at end of file diff --git a/support/templates/layouts/default.md b/support/templates/layouts/default.md new file mode 100644 index 00000000..00bb92af --- /dev/null +++ b/support/templates/layouts/default.md @@ -0,0 +1,3 @@ +# {%= title %} + +{% body %} \ No newline at end of file diff --git a/support/templates/middleware.md b/support/templates/middleware.md new file mode 100644 index 00000000..2eba88b3 --- /dev/null +++ b/support/templates/middleware.md @@ -0,0 +1,5 @@ +--- +title: Middleware +--- + +TODO \ No newline at end of file diff --git a/support/templates/old-readme.md b/support/templates/old-readme.md new file mode 100644 index 00000000..79be96f4 --- /dev/null +++ b/support/templates/old-readme.md @@ -0,0 +1,221 @@ +A project without documentation is like a project that doesn't exist. Verb solves this by making it dead simple to generate documentation, using simple markdown templates, with zero configuration required. + +**I just want to generate a readme, can verb do this?** + +Yes! Just run [verb-readme-generator][]. + + +## What is verb? + +Verb is a documentation build system that is simple and fast enough to use for generating a readme for a GitHub project, but powerful and smart enough to build the most complex documentation projects around. + +Verb is also highly pluggable, with first-class support for instance plugins, pipeline (gulp/vinyl) plugins, routes and middleware, any template engine, helpers, and more. + +## Features + +* Create markdown docs from templates +* Use any template engine, such as [handlebars][], [lodash][] or [swig][] +* Use templates, partials, or layouts +* Helper support (sync and async!) +* First class template collection support +* Instance plugins and pipeline plugins +* Middleware + +**Plugins** + +Verb has first-class plugin support, along with native support for [gulp][] plugins, so you can do things like: + +* [Ignore files][gulp-drafts] marked as "drafts" +* CSS minification or reduction +* Spin up a dev server +* SASS or LESS compiling and minification +* JavaScript minification, reduction or concatenation +* Asset copying or renaming +* Image compression +* HTML minification and linting +* RSS feeds + +**Developers** + +* Use custom code to your project's `verbfile.js` (like `gulpfile.js` or `Gruntfile.js`) +* Use globally or locally installed verb "generators" +* Support for sub-generators +* Generators can be composed of multiple single-responsibility generators + +## Quickstart + +**Install readme generator** + +The `verb-readme-generator` is a good example of what's possible with verb, and it's an easy way to get started. You can install the library with the following command: + +```sh +$ npm i -g verb-readme-generator +``` + +**.verb.md** + +You should now be able to generate your project's readme from a `.verb.md` template with the following command: + +```sh +$ verb readme +``` + +Or you can go a step further by adding a `verbfile.js` to your project, allowing you to customize verb with generators, plugins, middleware, helpers, templates and more! + +**verbfile.js** + +Next, create a `verbfile.js` with the following code: + +```js +module.exports = function(app) { + app.extendWith('verb-readme-generator'); + + // call the `readme` task from verb-readme-generator + app.task('default', ['readme']); +}; +``` + +## verbfile + +Your project's `verbfile.js` should either export an instance of Verb or a function. + +**Instance** + +```js +var verb = require('verb'); +var app = verb(); + +module.exports = app; +``` + +**Function** + +When a function is exported, we refer to these as [verb generators](#verb-generators). Verb generators can be locally or globally installed, and can be composed with other verb generators and/or [sub-generators](#sub-generators). + +```js +module.exports = function(verb) { + // "private" verb instance is created for this verbfile +}; +``` + +## CLI + +**Installing the CLI** + +To run verb from the command line, you'll need to install verb globally first. You can that now with the following command: + +```sh +$ npm i -g verb +``` + +This adds the `verb` command to your system path, allowing it to be run from any directory in a project. + +**How the Verb CLI works** + +When the `verb` command is run, the globally installed verb looks for a locally installed verb module using node's `require()` system. + +If a locally installed verb is found, the CLI loads the local installation of the verb library. If a local verb is not found, the global verb will be used. + +Next, verb applies the configuration from your `verbfile.js` and/or `.verb.json`, then executes any generators or tasks you've specified for verb to run. + +### Command prefixes + +The following flags can be used as "prefixes" to other commands for setting options from the command line: + +- `--option`: set options in memory +- `--data`: set data to be passed to templates in memory +- `--config`: persist options to package.json +- `--save`: persist options to a global config store + +### Commands + +{%= apidocs("lib/commands/*.js") %} + +### silent + +Typically, when running generators and tasks you'll see something like this in the terminal. + +![running-generators](https://cloud.githubusercontent.com/assets/383994/14978816/7449a5c6-10ec-11e6-9bac-07e482e915f2.gif) + +Since it's common for tasks to be composed of other smaller tasks, or for generators to be composed of other generators or sub-generators, sometimes it's necessary to prevent these sub-tasks or sub-generators from displaying in the terminal. + +To silence a specific task, + +### config + +Persist configuration settings to the `verb` object in `package.json`. + +```sh +$ verb --config +``` + +Most of the above CLI commands can be prefixed with `--config` to persist the value to package.json. + + +### save + +Persist options values to the global data store for `app` ([verb][], [assemble][], [generate][], [update][], etc) + +```sh +$ verb --save +``` + +Most of the above CLI commands can be prefixed with `--save` to persist the value to the global config store. + +### file + +Specify the file to use instead of `verbfile.js`. + +```sh +$ verb --file +``` + +**Example** + +```sh +$ verb --file foo.js +``` + +### cwd + +Specify the cwd to use + +```sh +$ verb --cwd= +``` + +**Example** + +```sh +$ verb --cwd="foo/bar" +``` + +Display the currently defined cwd: + +```sh +$ verb --cwd +``` + +#### tasks + +Set the default tasks to run for a project: + +```sh +$ verb --config=tasks: +``` + +**Example** + +```sh +# single task +$ verb --config=tasks:foo + +# array of tasks +$ verb --config=tasks:foo,bar,baz +``` + +## API +{%= apidocs("index.js") %} + +## Upgrading +{%= include("upgrading") %} diff --git a/support/templates/pipeline-plugins.md b/support/templates/pipeline-plugins.md new file mode 100644 index 00000000..487061bf --- /dev/null +++ b/support/templates/pipeline-plugins.md @@ -0,0 +1,5 @@ +--- +title: Pipeline plugins +--- + +TODO \ No newline at end of file diff --git a/support/templates/tasks.md b/support/templates/tasks.md new file mode 100644 index 00000000..648f5f50 --- /dev/null +++ b/support/templates/tasks.md @@ -0,0 +1,186 @@ +--- +title: Tasks +related: + doc: ['generators#running-generators', 'generators', 'generator-js'] +--- + +Tasks are used for wrapping code that should be executed at a later point, either when specified by command line or explicitly run when using the API. + + + +## Creating tasks + +Tasks are asynchronous functions that are registered by name using the `.task` method, and can be run using the `.build` method. + +## Running tasks + +Tasks can be run by command line or API. + +### Command line + +Pass the names of the tasks to run after the `generate` command. + +**Examples** + +Run task `foo`: + +```sh +generate foo +``` + +Run tasks `foo`, `bar` and `baz`: + +```sh +generate foo bar baz +``` + +**Conflict resolution** + +You might notice that [generators](generators.md) can also be run from the command line using the same syntax. Generate can usually determine whether you meant to call tasks or generators. Visit the [running generators](generators.md#running-generators) documentation for more information. + +## Task API + +### .task + +Create a task: + +```js +app.task(name, fn); +``` + +**Params** + +* `name` **{String}**: name of the task to register +* `fn` **{Function}**: asynchronous callback function, or es6 generator function + +**Example** + +```js +app.task('default', function(cb) { + // do task stuff (be sure to call the callback) + cb(); +}); +``` + +**Stream or callback** + +When using generate's file system API (`.src`/`.dest` etc), you can optionally return a stream instead of calling a callback. Either a callback must be called, or a stream must be returned, otherwise generate has no way of knowing when a task is complete. + +### .build + +Run one more tasks. + +**Params** + +* `names` **{String|Array|Function}**: names of one or more tasks to run, or callback function if you only want to run the [default task](#default-task) +* `callback`: callback function, invoked after all tasks have finished executing. The callback function exposes `err` as the only argument, with any errors that occurred during the execution of any tasks. + +**Example** + +```js +app.task('foo', function(cb) { + // do task stuff + cb(); +}); +app.task('foo', function(cb) { + // do task stuff + cb(); +}); + +app.build(['foo', 'bar'], function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` + +### .generate + +The `.generate` may also be used to run tasks. However, `.generate` can be used to run _tasks and generators_, thus it will also look for generators to run when a task is not found. + +_To ensure that only tasks are run, use the `.build` method._ + +See the [generators documentation](#generators) for more details. + + +## Task composition + +### Task dependencies + +When a task has "dependencies", this means that one or more other tasks need to finish before the task is executed. + +Dependencies can be passed as the second argument to the `.task` method. + +**Example** + +In the following example, task `foo` has dependencies `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz'], function(cb) { + // do task stuff + cb(); +}); +``` + +Task `foo` will not execute until tasks `bar` and `baz` have completed. + +### Alias tasks + +An "alias" task is a task with one or more dependencies and _no callback_. + +**Example** + +In this example, task `foo` is an alias for tasks `bar` and `baz`: + +```js +app.task('foo', ['bar', 'baz']); +``` + +In this example, task `foo` is an alias for task `baz` + +```js +app.task('foo', ['baz']); +``` + +## Task options + +An `options` object may be passed as the second argument to the `.task` method. + +**Example** + +```js +app.task('default', {foo: 'bar'}, function(cb) { + // do task stuff + cb(); +}); +``` + +### options.silent + +Silence logging output for a specific task. + + +**Example** + +```js +app.task('default', {silent: true}, function(cb) { + // do task stuff + cb(); +}); +``` + +## default task + +The `default` task is run automatically when a callback is passed as the only argument: + +```js +app.task('default', function(cb) { + // do task stuff + cb(); +}); + +// no need to specify "default", but you can if you want +app.build(function(err) { + if (err) return console.log(err); + console.log('done'); +}); +``` diff --git a/test/_suite.js b/test/_suite.js new file mode 100644 index 00000000..e8b7d440 --- /dev/null +++ b/test/_suite.js @@ -0,0 +1,22 @@ +'use strict'; + +process.env.GENERATE_TEST = true; +var generate = require('..'); +var runner = require('base-test-runner')(); +var suite = require('base-test-suite'); + +/** + * Run the tests in `base-test-suite` + */ + +runner.on('templates', function(file) { + var fn = require(file.path); + if (typeof fn === 'function') { + fn(generate); + } else { + throw new Error('expected ' + file.path + ' to export a function'); + } +}); + +runner.addFiles('templates', suite.test.templates); +runner.addFiles('templates', suite.test['assemble-core']); diff --git a/test/app.cli.js b/test/app.cli.js new file mode 100644 index 00000000..f341a2cf --- /dev/null +++ b/test/app.cli.js @@ -0,0 +1,20 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var App = require('..'); +var app; + +describe('app.cli', function() { + beforeEach(function() { + app = new App({cli: true}); + }); + + describe('app.cli.map', function() { + it('should add a property to app.cli', function() { + app.cli.map('abc', function() {}); + assert.equal(app.cli.keys.pop(), 'abc'); + }); + }); +}); + diff --git a/test/app.doc.js b/test/app.doc.js new file mode 100644 index 00000000..abf08797 --- /dev/null +++ b/test/app.doc.js @@ -0,0 +1,23 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app; + +describe('app', function() { + beforeEach(function() { + app = generate(); + app.create('docs'); + }); + + describe('add doc', function() { + it('should add docs to `app.views.docs`:', function() { + app.doc('a.hbs', {path: 'a.hbs', content: 'a'}); + app.doc('b.hbs', {path: 'b.hbs', content: 'b'}); + app.doc('c.hbs', {path: 'c.hbs', content: 'c'}); + assert(Object.keys(app.views.docs).length === 3); + }); + }); +}); diff --git a/test/app.docs.js b/test/app.docs.js new file mode 100644 index 00000000..cbab80e6 --- /dev/null +++ b/test/app.docs.js @@ -0,0 +1,25 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app; + +describe('app', function() { + beforeEach(function() { + app = generate(); + app.create('docs'); + }); + + describe('add docs', function() { + it('should add docs to `app.views.docs`:', function() { + app.docs({ + 'a.hbs': {path: 'a.hbs', content: 'a'}, + 'b.hbs': {path: 'b.hbs', content: 'b'}, + 'c.hbs': {path: 'c.hbs', content: 'c'} + }); + assert.equal(Object.keys(app.views.docs).length, 3); + }); + }); +}); diff --git a/test/app.extendWith.js b/test/app.extendWith.js new file mode 100644 index 00000000..49472494 --- /dev/null +++ b/test/app.extendWith.js @@ -0,0 +1,601 @@ +'use strict'; + +require('mocha'); +require('generate-foo/generator.js'); + +var path = require('path'); +var assert = require('assert'); +var npm = require('npm-install-global'); +var utils = require('generator-util'); +var gm = require('global-modules'); +var isAbsolute = require('is-absolute'); +var resolve = require('resolve'); +var Base = require('..'); +var base; + +var fixture = path.resolve.bind(path, __dirname, 'fixtures/generators'); +function resolver(search, app) { + try { + if (isAbsolute(search.name)) { + search.name = require.resolve(search.name); + } else { + search.name = resolve.sync(search.name, {basedir: gm}); + } + search.app = app.register(search.name, search.name); + } catch (err) {} +} + +describe('.extendWith', function() { + before(function(cb) { + if (!utils.exists(path.resolve(gm, 'generate-bar'))) { + npm.install('generate-bar', cb); + } else { + cb(); + } + }); + + beforeEach(function() { + base = new Base(); + base.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + base.on('unresolved', resolver); + }); + + it('should throw an error when a generator is not found', function(cb) { + try { + base.register('foo', function(app) { + app.extendWith('fofoofofofofof'); + }); + + base.getGenerator('foo'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'cannot find generator: "fofoofofofofof"'); + cb(); + } + }); + + it('should extend a generator with settings in the default generator', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.options.foo, 'bar'); + assert.equal(app.cache.data.foo, 'bar'); + count++; + next(); + }); + }); + + base.register('default', function(app) { + app.data({foo: 'bar'}); + app.option({foo: 'bar'}); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not extend tasks by default', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.task('default', function(next) { + assert(app.tasks.hasOwnProperty('default')); + assert(!app.tasks.hasOwnProperty('a')); + assert(!app.tasks.hasOwnProperty('b')); + assert(!app.tasks.hasOwnProperty('c')); + count++; + next(); + }); + }); + + base.register('default', function(app) { + app.task('a', function(next) { + next(); + }); + app.task('b', function(next) { + next(); + }); + app.task('c', function(next) { + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should get a named generator', function(cb) { + var count = 0; + + base.register('foo', function(app) { + app.extendWith('bar'); + count++; + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getGenerator('foo'); + assert.equal(count, 1); + cb(); + }); + + it('should extend a generator with a named generator', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getGenerator('foo'); + }); + + it('should extend a generator with an array of generators', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith(['bar', 'baz', 'qux']); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + }); + + base.register('baz', function(app) { + app.task('b', function() {}); + }); + + base.register('qux', function(app) { + app.task('c', function() {}); + }); + + base.getGenerator('foo'); + }); + + describe('invoke generators', function(cb) { + it('should extend with a generator instance', function(cb) { + base.register('foo', function(app) { + var bar = app.getGenerator('bar'); + app.extendWith(bar); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.isBar = true; + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getGenerator('foo'); + }); + + it('should invoke a named generator', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getGenerator('foo'); + }); + }); + + describe('extend generators', function(cb) { + it('should extend a generator with a generator invoked by name', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('bar', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getGenerator('foo'); + }); + + it('should extend a generator with a generator invoked by alias', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('qux'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('generate-qux', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getGenerator('qux'); + base.getGenerator('foo'); + }); + + it('should extend with a generator invoked by filepath', function(cb) { + base.register('foo', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith(fixture('qux')); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should extend with a generator invoked from node_modules by name', function(cb) { + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('generate-foo'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getGenerator('abc'); + }); + + it('should extend with a generator invoked from node_modules by name on a default instance', function() { + var app = new Base(); + + app.on('unresolved', resolver); + app.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('generate-foo'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + }); + + it('should use a generator from node_modules as a plugin', function() { + var app = new Base(); + + app.option('toAlias', function(name) { + return name.replace(/^generate-/, ''); + }); + + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.use(require('generate-foo')); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + }); + + it('should extend with a generator invoked from global modules by name', function(cb) { + base.register('zzz', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + app.extendWith('generate-bar'); + + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getGenerator('zzz'); + }); + + it('should extend with a generator invoked from global modules by alias', function(cb) { + base.register('generate-bar'); + + base.register('zzz', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('bar'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.getGenerator('zzz'); + }); + }); + + describe('sub-generators', function(cb) { + it('should invoke sub-generators', function(cb) { + base.register('foo', function(app) { + app.register('one', function(app) { + app.task('a', function() {}); + }); + app.register('two', function(app) { + app.task('b', function() {}); + }); + + app.extendWith('one'); + app.extendWith('two'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should invoke a sub-generator on the base instance', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getGenerator('foo'); + }); + + it('should invoke a sub-generator from node_modules by name', function(cb) { + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('xyz'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('xyz', function(app) { + app.extendWith('generate-foo'); + }); + + base.getGenerator('abc'); + }); + + it('should invoke a sub-generator from node_modules by alias', function(cb) { + base.register('generate-foo'); + + base.register('abc', function(app) { + assert(!app.tasks.a); + assert(!app.tasks.b); + assert(!app.tasks.c); + + app.extendWith('xyz'); + assert(app.tasks.a); + assert(app.tasks.b); + assert(app.tasks.c); + cb(); + }); + + base.register('xyz', function(app) { + app.extendWith('foo'); + }); + + base.getGenerator('abc'); + }); + + it('should invoke an array of sub-generators', function(cb) { + base.register('foo', function(app) { + app.register('one', function(app) { + app.task('a', function() {}); + }); + app.register('two', function(app) { + app.task('b', function() {}); + }); + + app.extendWith(['one', 'two']); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should invoke sub-generators from sub-generators', function(cb) { + base.register('foo', function(app) { + app.register('one', function(sub) { + sub.register('a', function(a) { + a.task('a', function() {}); + }); + }); + + app.register('two', function(sub) { + sub.register('a', function(a) { + a.task('b', function() {}); + }); + }); + + app.extendWith('one.a'); + app.extendWith('two.a'); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should invoke an array of sub-generators from sub-generators', function(cb) { + base.register('foo', function(app) { + app.register('one', function(sub) { + sub.register('a', function(a) { + a.task('a', function() {}); + }); + }); + + app.register('two', function(sub) { + sub.register('a', function(a) { + a.task('b', function() {}); + }); + }); + + app.extendWith(['one.a', 'two.a']); + + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should invoke sub-generator that invokes another generator', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.extendWith('baz'); + }); + + base.register('baz', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + }); + + base.getGenerator('foo'); + }); + + it('should invoke sub-generator that invokes another sub-generator', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.extendWith('baz.sub'); + }); + }); + + base.register('baz', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getGenerator('foo'); + }); + + it('should invoke sub-generator that invokes another sub-generator', function(cb) { + base.register('foo', function(app) { + app.extendWith('bar.sub'); + assert(app.tasks.hasOwnProperty('a')); + assert(app.tasks.hasOwnProperty('b')); + assert(app.tasks.hasOwnProperty('c')); + cb(); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.extendWith('baz.sub'); + }); + }); + + base.register('baz', function(app) { + app.register('sub', function(sub) { + sub.task('a', function() {}); + sub.task('b', function() {}); + sub.task('c', function() {}); + }); + }); + + base.getGenerator('foo'); + }); + }); +}); diff --git a/test/app.generate.js b/test/app.generate.js new file mode 100644 index 00000000..b1403d96 --- /dev/null +++ b/test/app.generate.js @@ -0,0 +1,961 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var config = require('base-config-process'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('config.process', function(cb) { + it('should run tasks when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(); + }); + + base.generate('default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(new Error('fooo')); + }); + + base.generate('default', function(err) { + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle config errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + + base.config.map('foo', function(val, key, config, next) { + count++; + next(new Error('fooo')); + }); + + base.set('cache.config', {foo: true}); + base.task('default', function(next) { + count--; + next(); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('generators', function(cb) { + it('should throw an error when a generator is not found', function(cb) { + base.generate('fdsslsllsfjssl', function(err) { + assert(err); + assert.equal('Cannot find generator: "fdsslsllsfjssl"', err.message); + cb(); + }); + }); + + it('should *not* throw an error when the default task is not found', function(cb) { + base.register('foo', function() {}); + base.generate('foo:default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default generator is not found', function(cb) { + base.generate('default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default task and default generator is not found', function(cb) { + base.generate('default:default', function(err) { + assert(!err); + cb(); + }); + }); + + // special case + it('should throw an error when a generator is not found in argv.cwd', function(cb) { + base.option('cwd', 'foo/bar/baz'); + base.generate('sflsjljskksl', function(err) { + assert(err); + assert.equal('Cannot find generator: "sflsjljskksl" in "foo/bar/baz"', err.message); + cb(); + }); + }); + + it('should throw an error when a task is not found (task array)', function(cb) { + var fn = console.error; + var res = []; + console.error = function(msg) { + res.push(msg); + }; + base.register('fdsslsllsfjssl', function() {}); + base.generate('fdsslsllsfjssl', ['foo'], function(err) { + console.error = fn; + if (err) return cb(err); + assert.equal(res[0], 'Cannot find task: "foo" in generator: "fdsslsllsfjssl"'); + cb(); + }); + }); + + it('should throw an error when a task is not found (task string)', function(cb) { + var fn = console.error; + var res = []; + console.error = function(msg) { + res.push(msg); + }; + base.register('fdsslsllsfjssl', function() {}); + base.generate('fdsslsllsfjssl:foo', function(err) { + console.error = fn; + if (err) return cb(err); + assert.equal(res[0], 'Cannot find task: "foo" in generator: "fdsslsllsfjssl"'); + cb(); + }); + }); + + it('should not reformat error messages that are not about invalid tasks', function(cb) { + base.task('default', function(cb) { + cb(new Error('whatever')); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + + it('should not throw an error when the default task is not defined', function(cb) { + base.register('foo', function() {}); + base.register('bar', function() {}); + base.generate('foo', ['default'], function(err) { + if (err) return cb(err); + + base.generate('bar', function(err) { + if (err) return cb(err); + + cb(); + }); + }); + }); + + it('should run a task on the instance', function(cb) { + base.task('abc123', function(next) { + next(); + }); + + base.generate('abc123', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run same-named task instead of a generator', function(cb) { + base.register('123xyz', function(app) { + cb(new Error('expected the task to run first')); + }); + + base.task('123xyz', function() { + cb(); + }); + + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task instead of a generator with a default task', function(cb) { + base.register('123xyz', function(app) { + app.task('default', function() { + cb(new Error('expected the task to run first')); + }); + }); + base.task('123xyz', function() { + cb(); + }); + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task on a same-named generator when the task is specified', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate('foo:default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks that includes a same-named generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('baz', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate(['foo:default', 'bar:baz'], function(err) { + assert(!err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run a generator from a task with the same name', function(cb) { + base.register('foo', function(app) { + app.task('default', function() { + cb(); + }); + }); + + base.task('foo', function(cb) { + base.generate('foo', cb); + }); + + base.build('foo', function(err) { + if (err) cb(err); + }); + }); + + it('should run the default task on a generator', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + next(); + }); + }); + + base.generate('foo', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run a stringified array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate('a,b,c', function(err) { + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run an array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of generators', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(['foo', 'bar'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run the default tasks on an array of generators', function(cb) { + var count = 0; + base.register('a', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('b', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('c', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + }); + + describe('options', function(cb) { + it('should pass options to generator.options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(app.options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should expose options on generator options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not mutate options on parent instance', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + assert(!base.options.foo); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('default tasks', function(cb) { + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('specified tasks', function(cb) { + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + }); + + describe('sub-generators', function(cb) { + it('should run the default task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task string on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task array on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an of stringified-tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an array of tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', ['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.register('qux', function(app) { + app.register('fez', function(fez) { + fez.task('default', function(next) { + count++; + next(); + }); + + fez.task('a', function(next) { + count++; + next(); + }); + + fez.task('b', function(next) { + count++; + next(); + }); + + fez.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate(['foo.bar:a,b,c', 'qux.fez:a,b,c'], function(err) { + if (err) return cb(err); + assert.equal(count, 6); + cb(); + }); + }); + }); + + describe('cross-generator', function(cb) { + it('should run a generator from another generator', function(cb) { + var res = ''; + + base.register('foo', function(app, two) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'foo > sub > default '; + base.generate('bar.sub', next); + }); + }); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'bar > sub > default '; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(res, 'foo > sub > default bar > sub > default '); + cb(); + }); + }); + + it('should run the specified task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('events', function(cb) { + it('should emit generate', function(cb) { + var count = 0; + + base.on('generate', function() { + count++; + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should expose the generator alias as the first parameter', function(cb) { + base.on('generate', function(name) { + assert.equal(name, 'sub'); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + }); + }); + + it('should expose the tasks array as the second parameter', function(cb) { + base.on('generate', function(name, tasks) { + assert.deepEqual(tasks, ['abc']); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + }); + }); + }); +}); diff --git a/test/app.generateEach.js b/test/app.generateEach.js new file mode 100644 index 00000000..1964250f --- /dev/null +++ b/test/app.generateEach.js @@ -0,0 +1,931 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var config = require('base-config-process'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('config.process', function(cb) { + it('should run tasks when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(); + }); + + base.generate('default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run handle errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.task('default', function(next) { + count++; + next(new Error('fooo')); + }); + + base.generate('default', function(err) { + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + + it('should handle config errors when the base-config plugin is used', function(cb) { + base.use(config()); + var count = 0; + base.config.map('foo', function(val, key, config, next) { + count++; + next(new Error('fooo')); + }); + + base.set('cache.config', {foo: true}); + + base.task('default', function(next) { + count--; + next(); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'fooo'); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('generators', function(cb) { + it('should throw an error when a generator is not found', function(cb) { + base.generate('fdsslsllsfjssl', function(err) { + assert(err); + assert.equal('Cannot find generator: "fdsslsllsfjssl"', err.message); + cb(); + }); + }); + + it('should *not* throw an error when the default task is not found', function(cb) { + base.register('foo', function() {}); + base.generate('foo:default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default generator is not found', function(cb) { + base.generate('default', function(err) { + assert(!err); + cb(); + }); + }); + + it('should not throw an error when a default task and default generator is not found', function(cb) { + base.generate('default:default', function(err) { + assert(!err); + cb(); + }); + }); + + // special case + it('should throw an error when a generator is not found in argv.cwd', function(cb) { + base.option('cwd', 'foo/bar/baz'); + base.generate('sflsjljskksl', function(err) { + assert(err); + assert.equal('Cannot find generator: "sflsjljskksl" in "foo/bar/baz"', err.message); + cb(); + }); + }); + + it('should not reformat error messages that are not about invalid tasks', function(cb) { + base.task('default', function(cb) { + cb(new Error('whatever')); + }); + + base.generate('default', function(err) { + assert(err); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + + it('should not throw an error when the default task is not defined', function(cb) { + base.register('foo', function() {}); + base.register('bar', function() {}); + base.generate('foo', ['default'], function(err) { + if (err) return cb(err); + + base.generate('bar', function(err) { + if (err) return cb(err); + + cb(); + }); + }); + }); + + it('should run a task on the instance', function(cb) { + base.task('abc123', function(next) { + next(); + }); + + base.generate('abc123', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run same-named task instead of a generator', function(cb) { + base.register('123xyz', function(app) { + cb(new Error('expected the task to run first')); + }); + + base.task('123xyz', function() { + cb(); + }); + + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task instead of a generator with a default task', function(cb) { + base.register('123xyz', function(app) { + app.task('default', function() { + cb(new Error('expected the task to run first')); + }); + }); + base.task('123xyz', function() { + cb(); + }); + base.generate('123xyz', function(err) { + assert(!err); + }); + }); + + it('should run a task on a same-named generator when the task is specified', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate('foo:default', function(err) { + assert(!err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks that includes a same-named generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('baz', function(next) { + count++; + next(); + }); + }); + + base.task('foo', function() { + cb(new Error('expected the generator to run')); + }); + + base.generate(['foo:default', 'bar:baz'], function(err) { + assert(!err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run a generator from a task with the same name', function(cb) { + base.register('foo', function(app) { + app.task('default', function() { + cb(); + }); + }); + + base.task('foo', function(cb) { + base.generate('foo', cb); + }); + + base.build('foo', function(err) { + if (err) cb(err); + }); + }); + + it('should run the default task on a generator', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + next(); + }); + }); + + base.generate('foo', function(err) { + assert(!err); + cb(); + }); + }); + + it('should run a stringified array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate('a,b,c', function(err) { + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run an array of tasks on the instance', function(cb) { + var count = 0; + base.task('a', function(next) { + count++; + next(); + }); + base.task('b', function(next) { + count++; + next(); + }); + base.task('c', function(next) { + count++; + next(); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of generators', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.register('bar', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(['foo', 'bar'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run the default tasks on an array of generators', function(cb) { + var count = 0; + base.register('a', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('b', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.register('c', function(app) { + this.task('default', function(cb) { + count++; + cb(); + }); + }); + + base.generate(['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + assert(!err); + cb(); + }); + }); + }); + + describe('options', function(cb) { + it('should pass options to generator.options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(app.options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should expose options on generator options', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should not mutate options on parent instance', function(cb) { + var count = 0; + base.register('default', function(app, base, env, options) { + app.task('default', function(next) { + count++; + assert.equal(options.foo, 'bar'); + assert(!base.options.foo); + next(); + }); + }); + + base.generate({foo: 'bar'}, function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('default tasks', function(cb) { + it('should run the default task on the default generator', function(cb) { + var count = 0; + base.register('default', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate(function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the default task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('specified tasks', function(cb) { + it('should run the specified task on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('abc', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an array of tasks on a registered generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.task('default', function(next) { + count++; + next(); + }); + + app.task('a', function(next) { + count++; + next(); + }); + + app.task('b', function(next) { + count++; + next(); + }); + + app.task('c', function(next) { + count++; + next(); + }); + }); + + base.generate('foo', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + }); + + describe('sub-generators', function(cb) { + it('should run the default task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task string on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run the specified task array on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should run an of stringified-tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar:a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an array of tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', ['a', 'b', 'c'], function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.bar', 'a,b,c', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run an multiple tasks on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('bar', function(bar) { + bar.task('default', function(next) { + count++; + next(); + }); + + bar.task('a', function(next) { + count++; + next(); + }); + + bar.task('b', function(next) { + count++; + next(); + }); + + bar.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.register('qux', function(app) { + app.register('fez', function(fez) { + fez.task('default', function(next) { + count++; + next(); + }); + + fez.task('a', function(next) { + count++; + next(); + }); + + fez.task('b', function(next) { + count++; + next(); + }); + + fez.task('c', function(next) { + count++; + next(); + }); + }); + }); + + base.generate(['foo.bar:a,b,c', 'qux.fez:a,b,c'], function(err) { + if (err) return cb(err); + assert.equal(count, 6); + cb(); + }); + }); + }); + + describe('cross-generator', function(cb) { + it('should run a generator from another generator', function(cb) { + var res = ''; + + base.register('foo', function(app, two) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'foo > sub > default '; + base.generate('bar.sub', next); + }); + }); + }); + + base.register('bar', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + res += 'bar > sub > default '; + next(); + }); + }); + }); + + base.generate('foo.sub', function(err) { + if (err) return cb(err); + assert.equal(res, 'foo > sub > default bar > sub > default '); + cb(); + }); + }); + + it('should run the specified task on a registered sub-generator', function(cb) { + var count = 0; + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + count++; + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + }); + + describe('events', function(cb) { + it('should emit generate', function(cb) { + var count = 0; + + base.on('generate', function() { + count++; + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + count++; + next(); + }); + }); + }); + + base.generate('foo.sub', ['abc'], function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should expose the generator alias as the first parameter', function(cb) { + base.on('generate', function(name) { + assert.equal(name, 'sub'); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + }); + }); + + it('should expose the tasks array as the second parameter', function(cb) { + base.on('generate', function(name, tasks) { + assert.deepEqual(tasks, ['abc']); + cb(); + }); + + base.register('foo', function(app) { + app.register('sub', function(sub) { + sub.task('default', function(next) { + next(); + }); + + sub.task('abc', function(next) { + next(); + }); + }); + }); + + base.generate('foo.sub:abc', function(err) { + if (err) return cb(err); + }); + }); + }); +}); diff --git a/test/app.generator.js b/test/app.generator.js new file mode 100644 index 00000000..88a5f66f --- /dev/null +++ b/test/app.generator.js @@ -0,0 +1,291 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname, 'fixtures'); + +describe('.generator', function() { + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^generate-(.*)/, '$1'); + }); + }); + + describe('generator', function() { + it('should get a generator by alias', function() { + base.register('generate-foo', require('generate-foo')); + var gen = base.getGenerator('foo'); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should get a generator using a custom lookup function', function() { + base.register('generate-foo', function() {}); + base.register('generate-bar', function() {}); + + var gen = base.getGenerator('foo', { + lookup: function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + } + }); + + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + }); + + describe('register > function', function() { + it('should register a generator function by name', function() { + base.generator('foo', function() {}); + assert(base.generators.hasOwnProperty('foo')); + }); + + it('should register a generator function by alias', function() { + base.generator('generate-abc', function() {}); + assert(base.generators.hasOwnProperty('generate-abc')); + }); + }); + + describe('get > alias', function() { + it('should get a generator by alias', function() { + base.generator('generate-abc', function() {}); + var abc = base.generator('abc'); + assert(abc); + assert.equal(typeof abc, 'object'); + }); + }); + + describe('get > name', function() { + it('should get a generator by name', function() { + base.generator('generate-abc', function() {}); + var abc = base.generator('generate-abc'); + assert(abc); + assert.equal(typeof abc, 'object'); + }); + }); + + describe('generators', function() { + it('should invoke a registered generator when `getGenerator` is called', function(cb) { + base.register('foo', function(app) { + app.task('default', function() {}); + cb(); + }); + base.getGenerator('foo'); + }); + + it('should expose the generator instance on `app`', function(cb) { + base.register('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.get('a'), 'b'); + next(); + }); + }); + + var foo = base.getGenerator('foo'); + foo.set('a', 'b'); + foo.build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose the "base" instance on `base`', function(cb) { + base.set('x', 'z'); + base.register('foo', function(app, base) { + app.task('default', function(next) { + assert.equal(base.get('x'), 'z'); + next(); + }); + }); + + var foo = base.getGenerator('foo'); + foo.set('a', 'b'); + foo.build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose the "env" object on `env`', function(cb) { + base.register('foo', function(app, base, env) { + app.task('default', function(next) { + assert.equal(env.alias, 'foo'); + next(); + }); + }); + + base.getGenerator('foo').build('default', function(err) { + if (err) return cb(err); + cb(); + }); + }); + + it('should expose an app\'s generators on app.generators', function(cb) { + base.register('foo', function(app) { + app.register('a', function() {}); + app.register('b', function() {}); + + app.generators.hasOwnProperty('a'); + app.generators.hasOwnProperty('b'); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should expose all root generators on base.generators', function(cb) { + base.register('foo', function(app, b) { + b.generators.hasOwnProperty('foo'); + b.generators.hasOwnProperty('bar'); + b.generators.hasOwnProperty('baz'); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) {}); + base.getGenerator('foo'); + }); + }); + + describe('cross-generators', function() { + it('should get a generator from another generator', function(cb) { + base.register('foo', function(app, b) { + var bar = b.getGenerator('bar'); + assert(bar); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) {}); + base.getGenerator('foo'); + }); + + it('should set options on another generator instance', function(cb) { + base.generator('foo', function(app) { + app.task('default', function(next) { + assert.equal(app.option('abc'), 'xyz'); + next(); + }); + }); + + base.generator('bar', function(app, b) { + var foo = b.getGenerator('foo'); + foo.option('abc', 'xyz'); + foo.build(function(err) { + if (err) return cb(err); + cb(); + }); + }); + }); + }); + + describe('generators > filepath', function() { + it('should register a generator function from a file path', function() { + var one = base.generator('one', fixtures('one/generator.js')); + assert(base.generators.hasOwnProperty('one')); + assert.equal(typeof base.generators.one, 'object'); + assert.deepEqual(base.generators.one, one); + }); + + it('should get a registered generator by name', function() { + var one = base.generator('one', fixtures('one/generator.js')); + var two = base.generator('two', fixtures('two/generator.js')); + assert(base.generator('one')); + assert(base.generator('two')); + }); + }); + + describe('tasks', function() { + it('should expose a generator\'s tasks on app.tasks', function(cb) { + base.register('foo', function(app) { + app.task('a', function() {}); + app.task('b', function() {}); + assert(app.tasks.a); + assert(app.tasks.b); + cb(); + }); + + base.getGenerator('foo'); + }); + + it('should get tasks from another generator', function(cb) { + base.register('foo', function(app, b) { + var baz = b.getGenerator('baz'); + var task = baz.tasks.aaa; + assert(task); + cb(); + }); + + base.register('bar', function(app, base) {}); + base.register('baz', function(app, base) { + app.task('aaa', function() {}); + }); + base.getGenerator('foo'); + }); + }); + + describe('namespace', function() { + it('should expose `app.namespace`', function(cb) { + base.generator('foo', function(app) { + assert(typeof app.namespace, 'string'); + cb(); + }); + }); + + it('should create namespace from generator alias', function(cb) { + base.generator('generate-foo', function(app) { + assert.equal(app.namespace, base._name + '.foo'); + cb(); + }); + }); + + it('should create sub-generator namespace from parent namespace and alias', function(cb) { + var name = base._name; + base.generator('generate-foo', function(app) { + assert.equal(app.namespace, name + '.foo'); + + app.generator('generate-bar', function(bar) { + assert.equal(bar.namespace, name + '.foo.bar'); + + bar.generator('generate-baz', function(baz) { + assert.equal(baz.namespace, name + '.foo.bar.baz'); + + baz.generator('generate-qux', function(qux) { + assert.equal(qux.namespace, name + '.foo.bar.baz.qux'); + cb(); + }); + }); + }); + }); + }); + + it('should expose namespace on `this`', function(cb) { + base.generator('generate-foo', function(app, first) { + assert.equal(this.namespace, base._name + '.foo'); + + this.generator('generate-bar', function() { + assert.equal(this.namespace, base._name + '.foo.bar'); + + this.generator('generate-baz', function() { + assert.equal(this.namespace, base._name + '.foo.bar.baz'); + + this.generator('generate-qux', function() { + assert.equal(this.namespace, base._name + '.foo.bar.baz.qux'); + assert.equal(app.namespace, base._name + '.foo'); + assert.equal(first.namespace, base._name); + cb(); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/app.getGenerator.js b/test/app.getGenerator.js new file mode 100644 index 00000000..0300fafd --- /dev/null +++ b/test/app.getGenerator.js @@ -0,0 +1,103 @@ +'use strict'; + +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('.generator', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should get a generator from the base instance', function() { + base.register('abc', function() {}); + var generator = base.getGenerator('abc'); + assert(generator); + assert.equal(typeof generator, 'object'); + assert.equal(generator.name, 'abc'); + }); + + it('should fail when a generator is not found', function() { + var generator = base.getGenerator('whatever'); + assert(!generator); + }); + + it('should get a generator from the base instance from a nested generator', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var generator = base.getGenerator('abc'); + assert(generator); + assert.equal(typeof generator, 'object'); + assert.equal(generator.name, 'abc'); + }); + }); + base.getGenerator('xyz'); + }); + + it('should get a generator from the base instance using `this`', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var generator = this.getGenerator('abc'); + assert(generator); + assert.equal(typeof generator, 'object'); + assert.equal(generator.name, 'abc'); + }); + }); + base.getGenerator('xyz'); + }); + + it('should get a base generator from "app" from a nested generator', function() { + base.register('abc', function() {}); + base.register('xyz', function(app) { + app.register('sub', function(sub) { + var generator = app.getGenerator('abc'); + assert(generator); + assert.equal(typeof generator, 'object'); + assert.equal(generator.name, 'abc'); + }); + }); + base.getGenerator('xyz'); + }); + + it('should get a nested generator', function() { + base.register('abc', function(abc) { + abc.register('def', function(def) {}); + }); + + var generator = base.getGenerator('abc.def'); + assert(generator); + assert.equal(typeof generator, 'object'); + assert.equal(generator.name, 'def'); + }); + + it('should get a deeply nested generator', function() { + base.register('abc', function(abc) { + abc.register('def', function(def) { + def.register('ghi', function(ghi) { + ghi.register('jkl', function(jkl) { + jkl.register('mno', function() {}); + }); + }); + }); + }); + + var generator = base.getGenerator('abc.def.ghi.jkl.mno'); + assert(generator); + assert.equal(typeof generator, 'object'); + assert.equal(generator.name, 'mno'); + }); + + it('should get a generator that was registered by path', function() { + base.register('a', fixtures('generators/a')); + var generator = base.getGenerator('a'); + + assert(generator); + assert(generator.tasks); + assert(generator.tasks.hasOwnProperty('default')); + }); +}); diff --git a/test/app.include.js b/test/app.include.js new file mode 100644 index 00000000..cdf58bbf --- /dev/null +++ b/test/app.include.js @@ -0,0 +1,26 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app, len; + +describe('app', function() { + beforeEach(function() { + app = generate(); + if (typeof app.include === 'undefined') { + app.create('include'); + } + len = Object.keys(app.views.includes).length; + }); + + describe('add include', function() { + it('should add includes to `app.views.includes`:', function() { + app.include('a.hbs', {path: 'a.hbs', content: 'a'}); + app.include('b.hbs', {path: 'b.hbs', content: 'b'}); + app.include('c.hbs', {path: 'c.hbs', content: 'c'}); + assert((Object.keys(app.views.includes).length - len) === 3); + }); + }); +}); diff --git a/test/app.includes.js b/test/app.includes.js new file mode 100644 index 00000000..2f3898b3 --- /dev/null +++ b/test/app.includes.js @@ -0,0 +1,28 @@ +'use strict'; + +require('mocha'); +require('should'); +var assert = require('assert'); +var generate = require('..'); +var app, len; + +describe('app', function() { + beforeEach(function() { + app = generate(); + if (typeof app.include === 'undefined') { + app.create('include'); + } + len = Object.keys(app.views.includes).length; + }); + + describe('add includes', function() { + it('should add includes to `app.views.includes`:', function() { + app.includes({ + 'a.hbs': {path: 'a.hbs', content: 'a'}, + 'b.hbs': {path: 'b.hbs', content: 'b'}, + 'c.hbs': {path: 'c.hbs', content: 'c'} + }); + assert((Object.keys(app.views.includes).length - len) === 3); + }); + }); +}); diff --git a/test/app.layout.js b/test/app.layout.js new file mode 100644 index 00000000..f7631579 --- /dev/null +++ b/test/app.layout.js @@ -0,0 +1,24 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.layout()', function() { + beforeEach(function() { + app = assemble(); + if (!app.layout) { + app.create('layout', {viewType: 'layout'}); + } + }); + + describe('add layout', function() { + it('should add layouts to `app.views.layouts`:', function() { + app.layout('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.layout('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.layout('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.layouts).length === 3); + }); + }); +}); diff --git a/test/app.layouts.js b/test/app.layouts.js new file mode 100644 index 00000000..74d972b5 --- /dev/null +++ b/test/app.layouts.js @@ -0,0 +1,26 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.layouts()', function() { + beforeEach(function() { + app = assemble(); + if (!app.layout) { + app.create('layout', {viewType: 'layout'}); + } + }); + + describe('add layouts', function() { + it('should add layouts to `app.views.layouts`:', function() { + app.layouts({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')} + }); + assert(Object.keys(app.views.layouts).length === 3); + }); + }); +}); diff --git a/test/app.lookupGenerator.js b/test/app.lookupGenerator.js new file mode 100644 index 00000000..7b4d1590 --- /dev/null +++ b/test/app.lookupGenerator.js @@ -0,0 +1,61 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.lookupGenerator', function() { + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^generate-(.*)/, '$1'); + }); + }); + + it('should get a generator using a custom lookup function', function() { + base.register('generate-foo', function() {}); + base.register('generate-bar', function() {}); + var gen = base.lookupGenerator('foo', function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + }); + + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should get a generator using a custom lookup function passed on options', function() { + base.register('generate-foo', function() {}); + base.register('generate-bar', function() {}); + + var gen = base.getGenerator('foo', { + lookup: function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + } + }); + + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should return undefined when nothing is found', function() { + var gen = base.lookupGenerator('fofofofofo', function(key) { + return ['generate-' + key, 'verb-' + key + '-generator', key]; + }); + + assert(!gen); + }); + + it('should throw an error when a function is not passed', function(cb) { + try { + base.lookupGenerator('foo'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected `fn` to be a lookup function'); + cb(); + } + }); +}); diff --git a/test/app.matchGenerator.js b/test/app.matchGenerator.js new file mode 100644 index 00000000..922d5de9 --- /dev/null +++ b/test/app.matchGenerator.js @@ -0,0 +1,57 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var spawn = require('cross-spawn'); +var exists = require('fs-exists-sync'); +var Base = require('..'); +var base; + +describe('.matchGenerator', function() { + before(function(cb) { + if (!exists(path.resolve(__dirname, '../node_modules/generate-foo'))) { + spawn('npm', ['install', '--global', 'generate-foo'], {stdio: 'inherit'}) + .on('error', cb) + .on('close', function(code, err) { + cb(err, code); + }); + } else { + cb(); + } + }); + + beforeEach(function() { + base = new Base(); + + base.option('toAlias', function(key) { + return key.replace(/^generate-(.*)/, '$1'); + }); + }); + + it('should match a generator by name', function() { + base.register('generate-foo'); + + var gen = base.matchGenerator('generate-foo'); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should match a generator by alias', function() { + base.register('generate-foo'); + + var gen = base.matchGenerator('foo'); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); + + it('should match a generator by path', function() { + base.register('generate-foo'); + var gen = base.matchGenerator(require.resolve('generate-foo')); + assert(gen); + assert.equal(gen.env.name, 'generate-foo'); + assert.equal(gen.env.alias, 'foo'); + }); +}); diff --git a/test/app.page.js b/test/app.page.js new file mode 100644 index 00000000..0f314f1b --- /dev/null +++ b/test/app.page.js @@ -0,0 +1,31 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.page()', function() { + beforeEach(function() { + app = assemble(); + if (!app.pages) { + app.create('pages'); + } + }); + + describe('add page', function() { + it('should add pages to `app.views.pages`:', function() { + app.page('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.page('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.page('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.pages).length === 3); + }); + }); + + describe('load', function() { + it('should load a page from a non-glob filepath', function() { + app.page('test/fixtures/pages/a.hbs'); + assert(Object.keys(app.views.pages).length === 1); + }); + }); +}); diff --git a/test/app.pages.js b/test/app.pages.js new file mode 100644 index 00000000..6be23244 --- /dev/null +++ b/test/app.pages.js @@ -0,0 +1,33 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var generate = require('..'); +var app; + +describe('.pages()', function() { + beforeEach(function() { + app = generate({cli: true}); + if (!app.pages) { + app.create('pages'); + } + }); + + describe('add pages', function() { + it('should add pages to `app.views.pages`:', function() { + app.pages({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')} + }); + assert.equal(Object.keys(app.views.pages).length, 3); + }); + }); + + describe('load pages', function() { + it('should load pages onto `app.views.pages`:', function() { + app.pages('test/fixtures/pages/*.hbs'); + assert.equal(Object.keys(app.views.pages).length, 3); + }); + }); +}); diff --git a/test/app.partial.js b/test/app.partial.js new file mode 100644 index 00000000..ba6ca577 --- /dev/null +++ b/test/app.partial.js @@ -0,0 +1,24 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.partial()', function() { + beforeEach(function() { + app = assemble(); + if (!app.partials) { + app.create('partials', {viewType: 'partial'}); + } + }); + + describe('add partial', function() { + it('should add partials to `app.views.partials`:', function() { + app.partial('a.hbs', {path: 'a.hbs', contents: new Buffer('a')}); + app.partial('b.hbs', {path: 'b.hbs', contents: new Buffer('b')}); + app.partial('c.hbs', {path: 'c.hbs', contents: new Buffer('c')}); + assert(Object.keys(app.views.partials).length === 3); + }); + }); +}); diff --git a/test/app.partials.js b/test/app.partials.js new file mode 100644 index 00000000..f8d06564 --- /dev/null +++ b/test/app.partials.js @@ -0,0 +1,27 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var assemble = require('..'); +var app; + +describe('.partials()', function() { + beforeEach(function() { + app = assemble(); + if (!app.partials) { + app.create('partials', {viewType: 'partial'}); + } + }); + + describe('add partials', function() { + it('should add partials to `app.views.partials`:', function() { + app.partials({ + 'a.hbs': {path: 'a.hbs', contents: new Buffer('a')}, + 'b.hbs': {path: 'b.hbs', contents: new Buffer('b')}, + 'c.hbs': {path: 'c.hbs', contents: new Buffer('c')} + }); + + assert(Object.keys(app.views.partials).length === 3); + }); + }); +}); diff --git a/test/app.questions.js b/test/app.questions.js new file mode 100644 index 00000000..f1cc4078 --- /dev/null +++ b/test/app.questions.js @@ -0,0 +1,294 @@ +'use strict'; + +process.env.NODE_ENV = 'test'; + +require('mocha'); +var assert = require('assert'); +var questions = require('base-questions'); +var config = require('base-config-process'); +var store = require('base-store'); +var App = require('..'); +var app, base, site; + +describe('app.questions', function() { + describe('plugin', function() { + beforeEach(function() { + base = new App(); + base.use(config()); + base.use(questions()); + base.use(store('base-questions-tests/base')); + + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/app')); + }); + + it('should expose a `questions` object on "app"', function() { + assert.equal(typeof app.questions, 'object'); + }); + + it('should expose a `set` method on "app.questions"', function() { + assert.equal(typeof app.questions.set, 'function'); + }); + it('should expose a `get` method on "app.questions"', function() { + assert.equal(typeof app.questions.get, 'function'); + }); + it('should expose an `ask` method on "app.questions"', function() { + assert.equal(typeof app.questions.ask, 'function'); + }); + + it('should expose an `ask` method on "app"', function() { + assert.equal(typeof app.ask, 'function'); + }); + it('should expose a `question` method on "app"', function() { + assert.equal(typeof app.question, 'function'); + }); + }); + + if (process.env.TRAVIS) { + return; + } + + describe('app.ask', function() { + beforeEach(function() { + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/ask')); + }); + + afterEach(function() { + app.store.del({force: true}); + app.questions.clear(); + app.cache.data = {}; + }); + + it.skip('should force all questions to be asked', function(cb) { + app.questions.option('init', 'author'); + app.ask({force: true}, function(err, answers) { + if (err) return cb(err); + console.log(answers); + cb(); + }); + }); + + it('should store a question:', function() { + app.question('a', 'b'); + assert(app.questions); + assert(app.questions.cache); + assert(app.questions.cache.a); + assert.equal(app.questions.cache.a.name, 'a'); + assert.equal(app.questions.cache.a.message, 'b'); + }); + + it.skip('should re-init a specific question:', function(cb) { + this.timeout(20000); + app.question('a', 'b'); + app.question('c', 'd'); + app.question('e', 'f'); + app.data({a: 'b'}); + + app.questions.get('e') + .force(); + + app.ask(function(err, answers) { + if (err) return cb(err); + console.log(answers); + cb(); + }); + }); + + it('should ask a question defined on `ask`', function(cb) { + app.data('name', 'Brian Woodward'); + + app.ask('name', function(err, answers) { + if (err) return cb(err); + assert.equal(answers.name, 'Brian Woodward'); + cb(); + }); + }); + + it('should ask a question and use a `cache.data` value to answer:', function(cb) { + app.question('a', 'this is a question'); + app.data('a', 'b'); + + app.ask('a', function(err, answers) { + if (err) return cb(err); + assert.equal(answers.a, 'b'); + + app.data('a', 'zzz'); + app.ask('a', function(err, answers) { + if (err) return cb(err); + assert.equal(answers.a, 'zzz'); + cb(); + }); + }); + }); + + it('should ask a question and use a `store.data` value to answer:', function(cb) { + app.question('a', 'this is another question'); + app.store.set('a', 'c'); + app.ask('a', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.a, 'c'); + cb(); + }); + }); + + it('should ask a question and use a config value to answer:', function(cb) { + app.question('a', 'b'); + app.config.process({data: {a: 'foo'}}, function(err) { + if (err) return cb(err); + + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.a, 'foo'); + cb(); + }); + }); + }); + + it('should prefer `cache.data` to `store.data`', function(cb) { + app.question('a', 'b'); + app.data('a', 'b'); + app.store.set('a', 'c'); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.a, 'b'); + cb(); + }); + }); + + it('should update data with data loaded by config', function(cb) { + app.question('a', 'this is a question'); + app.data('a', 'b'); + + app.config.process({data: {a: 'foo'}}, function(err) { + if (err) return cb(err); + + app.ask('a', function(err, answer) { + if (err) return cb(err); + + assert(answer); + assert.equal(answer.a, 'foo'); + cb(); + }); + }); + }); + }); + + describe('session data', function() { + before(function() { + site = new App(); + site.use(config()); + site.use(questions()); + site.use(store('base-questions-tests/site')); + + app = new App(); + app.use(config()); + app.use(questions()); + app.use(store('base-questions-tests/ask')); + }); + + after(function() { + site.store.del({force: true}); + site.questions.clear(); + + app.store.del({force: true}); + app.questions.clear(); + }); + + it('[app] should ask a question and use a `cache.data` value to answer:', function(cb) { + app.question('package.name', 'this is a question'); + app.data('package.name', 'base-questions'); + + app.ask('package.name', function(err, answers) { + if (err) return cb(err); + assert.equal(answers.package.name, 'base-questions'); + + app.data('package.name', 'foo-bar-baz'); + app.ask('package.name', function(err, answers) { + if (err) return cb(err); + assert.equal(answers.package.name, 'foo-bar-baz'); + cb(); + }); + }); + }); + + it('[site] should ask a question and use a `cache.data` value to answer:', function(cb) { + site.question('package.name', 'this is a question'); + site.data('package.name', 'base-questions'); + + site.ask('package.name', function(err, answers) { + if (err) return cb(err); + assert.equal(answers.package.name, 'base-questions'); + + site.data('package.name', 'foo-bar-baz'); + site.ask('package.name', function(err, answers) { + if (err) return cb(err); + assert.equal(answers.package.name, 'foo-bar-baz'); + cb(); + }); + }); + }); + + it('[app] should ask a question and use a `store.data` value to answer:', function(cb) { + app.question('author.name', 'this is another question'); + app.store.set('author.name', 'Brian Woodward'); + app.ask('author.name', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.author.name, 'Brian Woodward'); + cb(); + }); + }); + + it('[site] should ask a question and use a `store.data` value to answer:', function(cb) { + site.question('author.name', 'this is another question'); + site.store.set('author.name', 'Jon Schlinkert'); + site.ask('author.name', function(err, answers) { + if (err) return cb(err); + assert(answers); + assert.equal(answers.author.name, 'Brian Woodward'); + cb(); + }); + }); + + it('[app] should ask a question and use a config value to answer:', function(cb) { + app.question('foo', 'Username?'); + app.config.process({data: {foo: 'jonschlinkert'}}, function(err) { + if (err) return cb(err); + + app.store.set('foo', 'doowb'); + + app.ask('foo', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.foo, 'jonschlinkert'); + cb(); + }); + }); + }); + + it('[site] should ask a question and use a config value to answer:', function(cb) { + site.question('foo', 'Username?'); + site.config.process({data: {foo: 'doowb'}}, function(err) { + if (err) return cb(err); + + site.ask('foo', function(err, answer) { + if (err) return cb(err); + assert(answer); + assert.equal(answer.foo, 'doowb'); + cb(); + }); + }); + }); + }); +}); diff --git a/test/app.register.js b/test/app.register.js new file mode 100644 index 00000000..c5a5cd4b --- /dev/null +++ b/test/app.register.js @@ -0,0 +1,234 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var generators = require('base-generators'); +var Base = require('..'); +var base; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('.register', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('function', function() { + it('should register a generator a function', function() { + base.register('foo', function() {}); + var foo = base.getGenerator('foo'); + assert(foo); + assert.equal(foo.env.alias, 'foo'); + }); + + it('should get a task from a generator registered as a function', function() { + base.register('foo', function(foo) { + foo.task('default', function() {}); + }); + var generator = base.getGenerator('foo'); + assert(generator); + assert(generator.tasks); + assert(generator.tasks.hasOwnProperty('default')); + }); + + it('should get a generator from a generator registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) {}); + }); + var bar = base.getGenerator('foo.bar'); + assert(bar); + assert.equal(bar.env.alias, 'bar'); + }); + + it('should get a sub-generator from a generator registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.task('something', function() {}); + }); + }); + var bar = base.getGenerator('foo.bar'); + assert(bar); + assert(bar.tasks); + assert(bar.tasks.hasOwnProperty('something')); + }); + + it('should get a deeply-nested sub-generator registered as a function', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + qux.task('qux-one', function() {}); + }); + }); + }); + }); + + var qux = base.getGenerator('foo.bar.baz.qux'); + assert(qux); + assert(qux.tasks); + assert(qux.tasks.hasOwnProperty('qux-one')); + }); + + it('should expose the instance from each generator', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + qux.task('qux-one', function() {}); + }); + }); + }); + }); + + var qux = base + .getGenerator('foo') + .getGenerator('bar') + .getGenerator('baz') + .getGenerator('qux'); + + assert(qux); + assert(qux.tasks); + assert(qux.tasks.hasOwnProperty('qux-one')); + }); + + it('should fail when a generator that does not exist is defined', function() { + base.register('foo', function(foo) { + foo.register('bar', function(bar) { + bar.register('baz', function(baz) { + baz.register('qux', function(qux) { + }); + }); + }); + }); + var fez = base.getGenerator('foo.bar.fez'); + assert.equal(typeof fez, 'undefined'); + }); + + it('should expose the `base` instance as the second param', function(cb) { + base.register('foo', function(foo, base) { + assert(base.generators.hasOwnProperty('foo')); + cb(); + }); + base.getGenerator('foo'); + }); + + it('should expose sibling generators on the `base` instance', function(cb) { + base.register('foo', function(foo, base) { + foo.task('abc', function() {}); + }); + base.register('bar', function(bar, base) { + assert(base.generators.hasOwnProperty('foo')); + assert(base.generators.hasOwnProperty('bar')); + cb(); + }); + + base.getGenerator('foo'); + base.getGenerator('bar'); + }); + }); + + describe('alias', function() { + it('should use a custom function to create the alias', function() { + base.option('toAlias', function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }); + + base.register('base-abc-xyz', function() {}); + var xyz = base.getGenerator('xyz'); + assert(xyz); + assert.equal(xyz.env.alias, 'xyz'); + }); + }); + + describe('path', function() { + it('should register a generator function by name', function() { + base.register('foo', function() {}); + assert(base.generators.hasOwnProperty('foo')); + }); + + it('should register a generator function by alias', function() { + base.register('abc', function() {}); + assert(base.generators.hasOwnProperty('abc')); + }); + + it('should register a generator by dirname', function() { + base.register('a', fixtures('generators/a')); + assert(base.generators.hasOwnProperty('a')); + }); + + it('should register a generator from a configfile filepath', function() { + base.register('base-abc', fixtures('generators/a/generator.js')); + assert(base.generators.hasOwnProperty('base-abc')); + }); + + it('should throw when a generator does not expose the instance', function(cb) { + try { + base.register('not-exposed', require(fixtures('not-exposed.js'))); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'cannot resolve: \'not-exposed\''); + cb(); + } + }); + }); + + describe('instance', function() { + it('should register an instance', function() { + base.register('base-inst', new Base()); + assert(base.generators.hasOwnProperty('base-inst')); + }); + + it('should get a generator that was registered as an instance', function() { + var foo = new Base(); + foo.task('default', function() {}); + base.register('foo', foo); + assert(base.getGenerator('foo')); + }); + + it('should register multiple instances', function() { + var foo = new Base(); + var bar = new Base(); + var baz = new Base(); + base.register('foo', foo); + base.register('bar', bar); + base.register('baz', baz); + assert(base.getGenerator('foo')); + assert(base.getGenerator('bar')); + assert(base.getGenerator('baz')); + }); + + it('should get tasks from a generator that was registered as an instance', function() { + var foo = new Base(); + foo.task('default', function() {}); + base.register('foo', foo); + var generator = base.getGenerator('foo'); + assert(generator); + assert(generator.tasks.hasOwnProperty('default')); + }); + + it('should get sub-generators from a generator registered as an instance', function() { + var foo = new Base(); + foo.use(generators()); + foo.register('bar', function() {}); + base.register('foo', foo); + var generator = base.getGenerator('foo.bar'); + assert(generator); + }); + + it('should get tasks from sub-generators registered as an instance', function() { + var foo = new Base(); + foo.use(generators()); + + foo.options.isFoo = true; + foo.register('bar', function(bar) { + bar.task('whatever', function() {}); + }); + + base.register('foo', foo); + var generator = base.getGenerator('foo.bar'); + assert(generator.tasks); + assert(generator.tasks.hasOwnProperty('whatever')); + }); + }); +}); diff --git a/test/app.store.js b/test/app.store.js new file mode 100644 index 00000000..ca0137f6 --- /dev/null +++ b/test/app.store.js @@ -0,0 +1,332 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var store = require('base-store'); +var App = require('..'); +var app; + +describe('store', function() { + describe('methods', function() { + beforeEach(function() { + app = new App(); + app.use(store()); + app.store.create('app-data-tests'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should `.set()` a value on the store', function() { + app.store.set('one', 'two'); + assert.equal(app.store.data.one, 'two'); + }); + + it('should `.set()` an object', function() { + app.store.set({four: 'five', six: 'seven'}); + assert.equal(app.store.data.four, 'five'); + assert.equal(app.store.data.six, 'seven'); + }); + + it('should `.set()` a nested value', function() { + app.store.set('a.b.c.d', {e: 'f'}); + assert.equal(app.store.data.a.b.c.d.e, 'f'); + }); + + it('should `.union()` a value on the store', function() { + app.store.union('one', 'two'); + assert.deepEqual(app.store.data.one, ['two']); + }); + + it('should not union duplicate values', function() { + app.store.union('one', 'two'); + assert.deepEqual(app.store.data.one, ['two']); + + app.store.union('one', ['two']); + assert.deepEqual(app.store.data.one, ['two']); + }); + + it('should concat an existing array:', function() { + app.store.union('one', 'a'); + assert.deepEqual(app.store.data.one, ['a']); + + app.store.union('one', ['b']); + assert.deepEqual(app.store.data.one, ['a', 'b']); + + app.store.union('one', ['c', 'd']); + assert.deepEqual(app.store.data.one, ['a', 'b', 'c', 'd']); + }); + + it('should return true if a key `.has()` on the store', function() { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + assert(app.store.has('foo')); + assert(app.store.has('baz')); + assert(!app.store.has('bar')); + assert(!app.store.has('qux')); + }); + + it('should return true if a nested key `.has()` on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(!app.store.has('a.b.bar')); + assert(app.store.has('a.b.c.d')); + assert(app.store.has('a.b.c.d.x')); + assert(!app.store.has('a.b.c.d.z')); + assert(app.store.has('a.b.c.e')); + assert(app.store.has('a.b.c.e.f')); + assert(!app.store.has('a.b.c.e.z')); + assert(app.store.has('a.b.g.j')); + assert(!app.store.has('a.b.g.j.k')); + assert(!app.store.has('a.b.g.j.z')); + }); + + it('should return true if a key exists `.hasOwn()` on the store', function() { + app.store.set('foo', 'bar'); + app.store.set('baz', null); + app.store.set('qux', undefined); + + assert(app.store.hasOwn('foo')); + assert(!app.store.hasOwn('bar')); + assert(app.store.hasOwn('baz')); + assert(app.store.hasOwn('qux')); + }); + + it('should return true if a nested key exists `.hasOwn()` on the store', function() { + app.store.set('a.b.c.d', {x: 'zzz'}); + app.store.set('a.b.c.e', {f: null}); + app.store.set('a.b.g.j', {k: undefined}); + + assert(!app.store.hasOwn('a.b.bar')); + assert(app.store.hasOwn('a.b.c.d')); + assert(app.store.hasOwn('a.b.c.d.x')); + assert(!app.store.hasOwn('a.b.c.d.z')); + assert(app.store.has('a.b.c.e.f')); + assert(app.store.hasOwn('a.b.c.e.f')); + assert(!app.store.hasOwn('a.b.c.e.bar')); + assert(!app.store.has('a.b.g.j.k')); + assert(app.store.hasOwn('a.b.g.j.k')); + assert(!app.store.hasOwn('a.b.g.j.foo')); + }); + + it('should `.get()` a stored value', function() { + app.store.set('three', 'four'); + assert.equal(app.store.get('three'), 'four'); + }); + + it('should `.get()` a nested value', function() { + app.store.set({a: {b: {c: 'd'}}}); + assert.equal(app.store.get('a.b.c'), 'd'); + }); + + it('should `.del()` a stored value', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + assert(app.store.data.hasOwnProperty('a')); + assert(app.store.data.hasOwnProperty('c')); + + app.store.del('a'); + app.store.del('c'); + assert(!app.store.data.hasOwnProperty('a')); + assert(!app.store.data.hasOwnProperty('c')); + }); + + it('should `.del()` multiple stored values', function() { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + app.store.del(['a', 'c', 'e']); + assert.deepEqual(app.store.data, {}); + }); + }); +}); + +describe('create', function() { + beforeEach(function() { + app = new App({cli: true}); + app.use(store()); + app.store.create('abc'); + + // init the actual store json file + app.store.set('a', 'b'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should expose a `create` method', function() { + assert.equal(typeof app.store.create, 'function'); + }); + + it('should create a "sub-store" with the given name', function() { + var store = app.store.create('created'); + assert.equal(store.name, 'created'); + }); + + it('should create a "sub-store" with the project name when no name is passed', function() { + var store = app.store.create('app-store'); + assert.equal(store.name, 'app-store'); + }); + + it('should throw an error when a conflicting store name is used', function(cb) { + try { + app.store.create('create'); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'Cannot create store: "create", since "create" is a reserved property key. Please choose a different store name.'); + cb(); + } + }); + + it('should add a store object to store[name]', function() { + app.store.create('foo'); + assert.equal(typeof app.store.foo, 'object'); + assert.equal(typeof app.store.foo.set, 'function'); + app.store.foo.del({force: true}); + }); + + it('should save the store in a namespaced directory under the parent', function() { + app.store.create('foo'); + var dir = path.dirname(app.store.path); + + assert.equal(app.store.foo.path, path.join(dir, 'generate/foo.json')); + app.store.foo.set('a', 'b'); + app.store.foo.del({force: true}); + }); + + it('should set values on the custom store', function() { + app.store.create('foo'); + app.store.foo.set('a', 'b'); + assert.equal(app.store.foo.data.a, 'b'); + app.store.foo.del({force: true}); + }); + + it('should get values from the custom store', function() { + app.store.create('foo'); + app.store.foo.set('a', 'b'); + assert.equal(app.store.foo.get('a'), 'b'); + app.store.foo.del({force: true}); + }); +}); + +describe('events', function() { + beforeEach(function() { + app = new App({cli: true}); + app.use(store()); + app.store.create('abc'); + }); + + afterEach(function() { + app.store.data = {}; + app.store.del({force: true}); + app.options.cli = false; + }); + + it('should emit `set` when an object is set:', function() { + var keys = []; + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set({a: {b: {c: 'd'}}}); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when a key/value pair is set:', function() { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', 'b'); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when an object value is set:', function() { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set('a', {b: 'c'}); + assert.deepEqual(keys, ['a']); + }); + + it('should emit `set` when an array of objects is passed:', function(cb) { + var keys = []; + + app.store.on('set', function(key) { + keys.push(key); + }); + + app.store.set([{a: 'b'}, {c: 'd'}]); + assert.deepEqual(keys, ['a', 'c']); + cb(); + }); + + it('should emit `has`:', function(cb) { + app.store.on('has', function(val) { + assert(val); + cb(); + }); + + app.store.set('a', 'b'); + app.store.has('a'); + }); + + it('should emit `del` when a value is delted:', function(cb) { + app.store.on('del', function(keys) { + assert.deepEqual(keys, 'a'); + assert.equal(typeof app.store.get('a'), 'undefined'); + cb(); + }); + + app.store.set('a', {b: 'c'}); + assert.deepEqual(app.store.get('a'), {b: 'c'}); + app.store.del('a'); + }); + + it('should emit deleted keys on `del`:', function(cb) { + var arr = []; + + app.store.on('del', function(key) { + arr.push(key); + assert.equal(Object.keys(app.store.data).length, 0); + }); + + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + + app.store.del({force: true}); + assert.deepEqual(arr, ['a', 'c', 'e']); + cb(); + }); + + it('should throw an error if force is not passed', function(cb) { + app.store.set('a', 'b'); + app.store.set('c', 'd'); + app.store.set('e', 'f'); + + try { + app.store.del(); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'options.force is required to delete the entire cache.'); + cb(); + } + }); +}); diff --git a/test/app.task.js b/test/app.task.js new file mode 100644 index 00000000..68ad2123 --- /dev/null +++ b/test/app.task.js @@ -0,0 +1,191 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.generate', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should register a task', function() { + var fn = function(cb) { + cb(); + }; + base.task('default', fn); + assert.equal(typeof base.tasks.default, 'object'); + assert.equal(base.tasks.default.fn, fn); + }); + + it('should register a task with an array of dependencies', function(cb) { + var count = 0; + base.task('foo', function(next) { + count++; + next(); + }); + base.task('bar', function(next) { + count++; + next(); + }); + base.task('default', ['foo', 'bar'], function(next) { + count++; + next(); + }); + assert.equal(typeof base.tasks.default, 'object'); + assert.deepEqual(base.tasks.default.deps, ['foo', 'bar']); + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 3); + cb(); + }); + }); + + it('should run a glob of tasks', function(cb) { + var count = 0; + base.task('foo', function(next) { + count++; + next(); + }); + base.task('bar', function(next) { + count++; + next(); + }); + base.task('baz', function(next) { + count++; + next(); + }); + base.task('qux', function(next) { + count++; + next(); + }); + base.task('default', ['b*']); + assert.equal(typeof base.tasks.default, 'object'); + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 2); + cb(); + }); + }); + + it('should register a task with a list of strings as dependencies', function() { + base.task('default', 'foo', 'bar', function(cb) { + cb(); + }); + assert.equal(typeof base.tasks.default, 'object'); + assert.deepEqual(base.tasks.default.deps, ['foo', 'bar']); + }); + + it('should run a task', function(cb) { + var count = 0; + base.task('default', function(cb) { + count++; + cb(); + }); + + base.build('default', function(err) { + if (err) return cb(err); + assert.equal(count, 1); + cb(); + }); + }); + + it('should throw an error when a task with unregistered dependencies is run', function(cb) { + base.task('default', ['foo', 'bar']); + base.build('default', function(err) { + assert(err); + cb(); + }); + }); + + it('should throw an error when `.build` is called without a callback function.', function() { + try { + base.build('default'); + throw new Error('Expected an error to be thrown.'); + } catch (err) { + } + }); + + it('should emit task events', function(cb) { + var events = []; + base.on('task:starting', function(task) { + events.push('starting.' + task.name); + }); + base.on('task:finished', function(task) { + events.push('finished.' + task.name); + }); + base.on('task:error', function(e, task) { + events.push('error.' + task.name); + }); + base.task('foo', function(cb) { + cb(); + }); + base.task('bar', ['foo'], function(cb) { + cb(); + }); + base.task('default', ['bar']); + base.build('default', function(err) { + if (err) return cb(err); + assert.deepEqual(events, [ + 'starting.default', + 'starting.bar', + 'starting.foo', + 'finished.foo', + 'finished.bar', + 'finished.default' + ]); + cb(); + }); + }); + + it('should emit an error event when an error is passed back in a task', function(cb) { + base.on('error', function(err) { + assert(err); + assert.equal(err.message, 'This is an error'); + }); + base.task('default', function(cb) { + return cb(new Error('This is an error')); + }); + base.build('default', function(err) { + if (err) return cb(); + cb(new Error('Expected an error')); + }); + }); + + it('should emit an error event when an error is thrown in a task', function(cb) { + base.on('error', function(err) { + assert(err); + assert.equal(err.message, 'This is an error'); + }); + base.task('default', function(cb) { + cb(new Error('This is an error')); + }); + base.build('default', function(err) { + assert(err); + cb(); + }); + }); + + it('should run dependencies before running the dependent task.', function(cb) { + var seq = []; + base.task('foo', function(cb) { + seq.push('foo'); + cb(); + }); + base.task('bar', function(cb) { + seq.push('bar'); + cb(); + }); + base.task('default', ['foo', 'bar'], function(cb) { + seq.push('default'); + cb(); + }); + + base.build('default', function(err) { + if (err) return cb(err); + assert.deepEqual(seq, ['foo', 'bar', 'default']); + cb(); + }); + }); +}); diff --git a/test/app.toAlias.js b/test/app.toAlias.js new file mode 100644 index 00000000..e5f8bf44 --- /dev/null +++ b/test/app.toAlias.js @@ -0,0 +1,41 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('.toAlias', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should not create an alias when no prefix is given', function() { + assert.equal(base.toAlias('foo-bar'), 'foo-bar'); + }); + + it('should create an alias using the `options.toAlias` function', function() { + var alias = base.toAlias('one-two-three', { + toAlias: function(name) { + return name.slice(name.lastIndexOf('-') + 1); + } + }); + assert.equal(alias, 'three'); + }); + + it('should create an alias using the given function', function() { + var alias = base.toAlias('one-two-three', function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }); + assert.equal(alias, 'three'); + }); + + it('should create an alias using base.options.toAlias function', function() { + base.options.toAlias = function(name) { + return name.slice(name.lastIndexOf('-') + 1); + }; + + var alias = base.toAlias('one-two-three'); + assert.equal(alias, 'three'); + }); +}); diff --git a/test/fixtures/apidocs-comments.js b/test/fixtures/apidocs-comments.js deleted file mode 100644 index ebb9c1ae..00000000 --- a/test/fixtures/apidocs-comments.js +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Banner - */ - -/** - * Assign `value` to `key` - * - * @param {String} a - * @param {*} b - * @api public - */ - -function set(a, b, c) { - // do stuff -} diff --git a/test/fixtures/auto-loading/a.js b/test/fixtures/auto-loading/a.js deleted file mode 100644 index 98594c1b..00000000 --- a/test/fixtures/auto-loading/a.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = function a(noun) { - noun.a = 'aaa'; - return noun; -}; diff --git a/test/fixtures/auto-loading/a.md b/test/fixtures/auto-loading/a.md deleted file mode 100644 index 5f898d8d..00000000 --- a/test/fixtures/auto-loading/a.md +++ /dev/null @@ -1 +0,0 @@ -# this is a fixture \ No newline at end of file diff --git a/test/fixtures/auto-loading/b.js b/test/fixtures/auto-loading/b.js deleted file mode 100644 index de8fa98a..00000000 --- a/test/fixtures/auto-loading/b.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = function b(noun) { - noun.b = 'bbb'; - return noun; -}; diff --git a/test/fixtures/auto-loading/c.js b/test/fixtures/auto-loading/c.js deleted file mode 100644 index bddcc0bb..00000000 --- a/test/fixtures/auto-loading/c.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = function c(noun) { - noun.c = 'ccc'; - return noun; -}; diff --git a/test/fixtures/auto-loading/package.json b/test/fixtures/auto-loading/package.json deleted file mode 100644 index 2a7aa652..00000000 --- a/test/fixtures/auto-loading/package.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "name": "verb", - "description": "Verb makes it dead simple to generate markdown documentation, using simple templates, with zero configuration required. A project without documentation is like a project that doesn't exist.", - "version": "0.4.5", - "homepage": "https://github.com/verbose/verb", - "author": { - "name": "Jon Schlinkert", - "url": "https://github.com/jonschlinkert" - }, - "repository": "verbose/verb", - "bugs": { - "url": "https://github.com/verbose/verb/issues" - }, - "license": "MIT", - "files": [ - "lib/", - "index.js" - ], - "main": "index.js", - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "async": "^0.9.0", - "chalk": "^0.5.1", - "cwd": "^0.4.0", - "debug": "^2.1.1", - "engine-lodash": "^0.5.0", - "event-stream": "^3.2.2", - "export-files": "^1.0.0", - "extend-shallow": "^0.2.0", - "for-in": "^0.1.3", - "globby": "^1.1.0", - "gulp-util": "^3.0.3", - "helper-apidocs": "^0.2.1", - "helper-copyright": "^1.1.1", - "helper-date": "^0.2.0", - "helper-license": "^0.1.3", - "load-plugins": "^1.0.1", - "logging-helpers": "^0.3.0", - "map-files": "^0.3.0", - "merge-deep": "^0.1.3", - "object.reduce": "^0.1.3", - "orchestrator": "^0.3.7", - "parse-author": "^0.1.0", - "parse-copyright": "^0.3.1", - "repo-utils": "^0.1.1", - "session-cache": "^0.1.3", - "template": "^0.10.1", - "template-utils": "^0.4.1", - "through2": "^0.6.3", - "verb-readme-badges": "^0.2.0", - "verb-readme-includes": "^0.3.0", - "vinyl-fs": "^0.3.13", - "write-yaml": "^0.1.2" - }, - "devDependencies": { - "consolidate": "^0.11.0", - "gulp-istanbul": "^0.6.0", - "gulp-jshint": "^1.9.2", - "gulp-mocha": "^2.0.0", - "handlebars": "^3.0.0", - "jshint-stylish": "^1.0.0", - "lodash": "^3.2.0", - "mocha": "^2.0.1", - "should": "^4.3.0", - "swig": "^1.4.2" - }, - "keywords": [ - "comment", - "comments", - "doc", - "docs", - "document", - "documentation", - "generate", - "generator", - "gh", - "gh-pages", - "markdown", - "md", - "pages", - "readme", - "repo", - "repository", - "template", - "templates", - "verb", - "verbiage" - ] -} \ No newline at end of file diff --git a/test/fixtures/data/a.yml b/test/fixtures/data/a.yml deleted file mode 100644 index 718c772c..00000000 --- a/test/fixtures/data/a.yml +++ /dev/null @@ -1 +0,0 @@ -one: two diff --git a/test/fixtures/def-gen.js b/test/fixtures/def-gen.js new file mode 100644 index 00000000..9955ea4a --- /dev/null +++ b/test/fixtures/def-gen.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('default'); + cb(); + }); +}; diff --git a/test/fixtures/docs/a.md b/test/fixtures/docs/a.md deleted file mode 100644 index f7d1f887..00000000 --- a/test/fixtures/docs/a.md +++ /dev/null @@ -1,17 +0,0 @@ -# AAA - -> blockquote - -some content - -## One - -Section one - -## Two - -Section two - -## Three - -Section three. diff --git a/test/fixtures/docs/b.md b/test/fixtures/docs/b.md deleted file mode 100644 index 1905bbee..00000000 --- a/test/fixtures/docs/b.md +++ /dev/null @@ -1,17 +0,0 @@ -# BBB - -> blockquote - -some content - -## One - -Section one - -## Two - -Section two - -## Three - -Section three. diff --git a/test/fixtures/generator.js b/test/fixtures/generator.js new file mode 100644 index 00000000..3dbcc451 --- /dev/null +++ b/test/fixtures/generator.js @@ -0,0 +1,49 @@ +'use strict'; + +module.exports = function(app) { + app.register('generate-aaa', function(app) { + app.task('default', function(cb) { + console.log('generate > default'); + cb(); + }); + + app.register('sub', function(sub) { + sub.task('default', function(cb) { + console.log('aaa > sub > default'); + cb(); + }); + + sub.register('bbb', function(bbb) { + bbb.task('default', function(cb) { + console.log('aaa > sub > bbb > default'); + cb(); + }); + }); + }); + }); + + app.register('generate-abc', 'test/fixtures/generators/a/generator.js'); + + app.register('generate-bbb', function(app) { + app.task('default', function(cb) { + app.generate('aaa.sub.bbb', 'default', cb); + }); + }); + + app.register('generate-ccc', function(app) { + app.task('default', function(cb) { + app.generate('abc', 'default', cb); + }); + }); + + app.register('generate-ddd', function(app) { + app.task('default', function(cb) { + app.generate('abc.docs', 'x', cb); + }); + }); + + app.generate('aaa.sub', ['default'], function(err) { + if (err) throw err; + console.log('done'); + }); +}; diff --git a/test/fixtures/generators/a/.gitignore b/test/fixtures/generators/a/.gitignore new file mode 100644 index 00000000..80a228ca --- /dev/null +++ b/test/fixtures/generators/a/.gitignore @@ -0,0 +1,15 @@ +*.DS_Store +*.sublime-* +_gh_pages +bower_components +node_modules +npm-debug.log +actual +test/actual +temp +tmp +TODO.md +vendor +.idea +benchmark +coverage diff --git a/test/fixtures/generators/a/generator.js b/test/fixtures/generators/a/generator.js new file mode 100644 index 00000000..0198ef6e --- /dev/null +++ b/test/fixtures/generators/a/generator.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('fixtures/a > default'); + cb(); + }); +}; diff --git a/test/fixtures/generators/a/package.json b/test/fixtures/generators/a/package.json new file mode 100644 index 00000000..bb51a953 --- /dev/null +++ b/test/fixtures/generators/a/package.json @@ -0,0 +1,7 @@ +{ + "name": "generator-a", + "private": true, + "version": "0.1.0", + "files": ["index.js"], + "main": "generator.js" +} diff --git a/test/fixtures/generators/a/post.hbs b/test/fixtures/generators/a/post.hbs new file mode 100644 index 00000000..b2cb52b9 --- /dev/null +++ b/test/fixtures/generators/a/post.hbs @@ -0,0 +1,5 @@ +--- +title: Post +--- + +This is SOME POST \ No newline at end of file diff --git a/test/fixtures/generators/a/verbfile.js b/test/fixtures/generators/a/verbfile.js new file mode 100644 index 00000000..c3f4d75e --- /dev/null +++ b/test/fixtures/generators/a/verbfile.js @@ -0,0 +1,10 @@ +'use strict'; + +var path = require('path'); + +module.exports = function(app) { + app.task('default', function(cb) { + console.log('fixtures/a > default'); + cb(); + }); +}; diff --git a/test/fixtures/generators/qux/.gitignore b/test/fixtures/generators/qux/.gitignore new file mode 100644 index 00000000..80a228ca --- /dev/null +++ b/test/fixtures/generators/qux/.gitignore @@ -0,0 +1,15 @@ +*.DS_Store +*.sublime-* +_gh_pages +bower_components +node_modules +npm-debug.log +actual +test/actual +temp +tmp +TODO.md +vendor +.idea +benchmark +coverage diff --git a/test/fixtures/generators/qux/generator.js b/test/fixtures/generators/qux/generator.js new file mode 100644 index 00000000..887c0581 --- /dev/null +++ b/test/fixtures/generators/qux/generator.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/fixtures/generators/qux/package.json b/test/fixtures/generators/qux/package.json new file mode 100644 index 00000000..b1ec356a --- /dev/null +++ b/test/fixtures/generators/qux/package.json @@ -0,0 +1,7 @@ +{ + "name": "generate-qux", + "private": true, + "version": "0.1.0", + "files": ["generator.js"], + "main": "generator.js" +} diff --git a/test/fixtures/generators/qux/verbfile.js b/test/fixtures/generators/qux/verbfile.js new file mode 100644 index 00000000..28979eea --- /dev/null +++ b/test/fixtures/generators/qux/verbfile.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * testing... + */ + +module.exports = function(app, base, env) { + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); +}; diff --git a/test/fixtures/includes/a.md b/test/fixtures/includes/a.md deleted file mode 100644 index 64b375b1..00000000 --- a/test/fixtures/includes/a.md +++ /dev/null @@ -1 +0,0 @@ -This is a.md diff --git a/test/fixtures/middleware/copyright.js b/test/fixtures/middleware/copyright.js deleted file mode 100644 index 77eef3bd..00000000 --- a/test/fixtures/middleware/copyright.js +++ /dev/null @@ -1,12 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -/** - * This is a fixture for the middleware copyright test - */ diff --git a/test/fixtures/middleware/multi-toc-options.md b/test/fixtures/middleware/multi-toc-options.md deleted file mode 100644 index 4a1a8a7f..00000000 --- a/test/fixtures/middleware/multi-toc-options.md +++ /dev/null @@ -1,21 +0,0 @@ -# TOC fixture - -> random comments in a blockquote because I don't know how to be creative when I'm writing fixtures. - -## Table of Contents - - - -## Heading 1 - -Some content. - -## Heading 2 - -More content. - -### Heading 3 - -More content. - -### Heading 4 diff --git a/test/fixtures/middleware/multi-toc.md b/test/fixtures/middleware/multi-toc.md deleted file mode 100644 index c238eeca..00000000 --- a/test/fixtures/middleware/multi-toc.md +++ /dev/null @@ -1,21 +0,0 @@ -# TOC fixture - -> random comments in a blockquote because I don't know how to be creative when I'm writing fixtures. - -## Table of Contents - - - -## Heading 1 - -Some content. - -## Heading 2 - -More content. - -### Heading 3 - -More content. - -### Heading 4 diff --git a/test/fixtures/middleware/toc.md b/test/fixtures/middleware/toc.md deleted file mode 100644 index d8e20182..00000000 --- a/test/fixtures/middleware/toc.md +++ /dev/null @@ -1,21 +0,0 @@ -# TOC fixture - -> random comments in a blockquote because I don't know how to be creative when I'm writing fixtures. - -## Table of Contents - - - -## Heading 1 - -Some content. - -## Heading 2 - -More content. - -### Heading 3 - -More content. - -### Heading 4 diff --git a/test/fixtures/middleware/todo.js b/test/fixtures/middleware/todo.js deleted file mode 100644 index 175048ce..00000000 --- a/test/fixtures/middleware/todo.js +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Random banner - */ - -function foo() {} - -/** - * @TODO: this is todo #1 --> - * - * some random text - * @todo:this is todo #2 --> - * @todo: this is todo #3 --> - */ - -function bar() {} diff --git a/test/fixtures/not-exposed.js b/test/fixtures/not-exposed.js new file mode 100644 index 00000000..5cee793f --- /dev/null +++ b/test/fixtures/not-exposed.js @@ -0,0 +1,8 @@ +'use strict'; + +var Base = require('../..'); +var base = new Base({isApp: true}); + +base.register('not-exposed', function(app) { + +}); diff --git a/test/fixtures/one/generator.js b/test/fixtures/one/generator.js new file mode 100644 index 00000000..eb51b6d7 --- /dev/null +++ b/test/fixtures/one/generator.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports = function(app, base, env) { + app.task('default', function(cb) { + console.log('one > default'); + cb(); + }); + + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + + app.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; diff --git a/test/fixtures/one/index.js b/test/fixtures/one/index.js new file mode 100644 index 00000000..f2eca0ed --- /dev/null +++ b/test/fixtures/one/index.js @@ -0,0 +1 @@ +module.exports = require('./generator'); \ No newline at end of file diff --git a/test/fixtures/one/package.json b/test/fixtures/one/package.json new file mode 100644 index 00000000..abdc1f4f --- /dev/null +++ b/test/fixtures/one/package.json @@ -0,0 +1,30 @@ +{ + "name": "one", + "description": "The most interesting project in the world > Verb", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/one", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/one", + "bugs": { + "url": "https://github.com/jonschlinkert/one/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "generator.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "resolve-modules": "^0.1.2" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [] +} diff --git a/test/fixtures/one/templates/a.txt b/test/fixtures/one/templates/a.txt new file mode 100644 index 00000000..2ff82506 --- /dev/null +++ b/test/fixtures/one/templates/a.txt @@ -0,0 +1 @@ +one: aaa \ No newline at end of file diff --git a/test/fixtures/one/templates/x.txt b/test/fixtures/one/templates/x.txt new file mode 100644 index 00000000..139e1d8e --- /dev/null +++ b/test/fixtures/one/templates/x.txt @@ -0,0 +1 @@ +one: xxx \ No newline at end of file diff --git a/test/fixtures/one/templates/y.txt b/test/fixtures/one/templates/y.txt new file mode 100644 index 00000000..7308ea90 --- /dev/null +++ b/test/fixtures/one/templates/y.txt @@ -0,0 +1 @@ +one: yyy \ No newline at end of file diff --git a/test/fixtures/one/templates/z.txt b/test/fixtures/one/templates/z.txt new file mode 100644 index 00000000..04c378ad --- /dev/null +++ b/test/fixtures/one/templates/z.txt @@ -0,0 +1 @@ +one: zzz \ No newline at end of file diff --git a/test/fixtures/one/verbfile.js b/test/fixtures/one/verbfile.js new file mode 100644 index 00000000..546d0048 --- /dev/null +++ b/test/fixtures/one/verbfile.js @@ -0,0 +1,19 @@ +'use strict'; + + +module.exports = function(app, base, env) { + app.task('default', function(cb) { + console.log('one > default'); + cb(); + }); + + app.task('a', function() {}); + app.task('b', function() {}); + app.task('c', function() {}); + + app.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); + }); +}; \ No newline at end of file diff --git a/test/fixtures/package.json b/test/fixtures/package.json index 01265e5f..ed77450a 100644 --- a/test/fixtures/package.json +++ b/test/fixtures/package.json @@ -1,60 +1,9 @@ { - "name": "foo", - "description": "bar.", - "version": "25.0", - "homepage": "https://github.com/verbose/verb", - "author": { - "name": "Jon Schlinkert", - "url": "https://github.com/jonschlinkert" - }, - "repository": "verbose/verb", - "bugs": { - "url": "https://github.com/verbose/verb/issues" - }, - "license": "MIT", - "files": [ - "lib/", - "index.js" - ], + "name": "generate-tests", + "version": "0.0.0", + "private": true, + "description": "", "main": "index.js", - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "async": "^0.9.0", - "chalk": "^0.5.1", - "cwd": "^0.4.0", - "debug": "^2.1.1", - "diff": "^1.2.2" - }, - "devDependencies": { - "lodash": "^3.3.0", - "should": "^5.0.1", - "swig": "^1.4.2" - }, - "keywords": [ - "comment", - "comments", - "doc", - "docs", - "document", - "documentation", - "generate", - "generator", - "gh", - "gh-pages", - "markdown", - "md", - "pages", - "readme", - "repo", - "repository", - "template", - "templates", - "verb", - "verbiage" - ] + "license": "MIT", + "base": {} } diff --git a/test/fixtures/pages/a.hbs b/test/fixtures/pages/a.hbs new file mode 100644 index 00000000..51320bdc --- /dev/null +++ b/test/fixtures/pages/a.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/b.hbs b/test/fixtures/pages/b.hbs new file mode 100644 index 00000000..51320bdc --- /dev/null +++ b/test/fixtures/pages/b.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/pages/c.hbs b/test/fixtures/pages/c.hbs new file mode 100644 index 00000000..51320bdc --- /dev/null +++ b/test/fixtures/pages/c.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

<%= title() %>

\ No newline at end of file diff --git a/test/fixtures/templates/README-with-include.md b/test/fixtures/templates/README-with-include.md deleted file mode 100644 index e20115b3..00000000 --- a/test/fixtures/templates/README-with-include.md +++ /dev/null @@ -1,3 +0,0 @@ -# Success! - -{%= include("a.md") %} diff --git a/test/fixtures/templates/README.md b/test/fixtures/templates/README.md deleted file mode 100644 index 6199cb82..00000000 --- a/test/fixtures/templates/README.md +++ /dev/null @@ -1 +0,0 @@ -# Success! \ No newline at end of file diff --git a/test/fixtures/templates/helpers.md b/test/fixtures/templates/helpers.md deleted file mode 100644 index be146064..00000000 --- a/test/fixtures/templates/helpers.md +++ /dev/null @@ -1,42 +0,0 @@ - -{%%= badge() %} - -{%%= changelog() %} - -{%%= changelog("history.yml") %} - -{%%= contrib('authors') %} - -{%%= copyright() %} - -{%%= copyright('2010') %} - -{%%= date('YYYY-MM-DD') %} - -{%= date('YYYY-MM-DD') %} - -{%%= docs('install') %} - -{%%= html() %} - -{%%= include('footer.md') %} - -{%%= log() %} - -{%%= methods('foo.js') %} - -{%%= methods('foo.js', {template: 'yaml'}) %} - -{%%= moment() %} - -{%%= raw() %} - -# Table of Contents - -{%%= toc() %} - -{%%= comments('foo/*.js') %} - -{%%= authors() %} - -{%%= foo('name') %} diff --git a/test/fixtures/two/generator.js b/test/fixtures/two/generator.js new file mode 100644 index 00000000..eb9b49ce --- /dev/null +++ b/test/fixtures/two/generator.js @@ -0,0 +1,21 @@ +'use strict'; + +var Base = require('../../..'); +var base = new Base(); + +base.task('default', function() {}); +base.task('a', function() {}); +base.task('b', function() {}); +base.task('c', function() {}); + +base.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); +}); + +/** + * Expose this instance of `Generate` + */ + +module.exports = base; diff --git a/test/fixtures/two/package.json b/test/fixtures/two/package.json new file mode 100644 index 00000000..0f9fde1f --- /dev/null +++ b/test/fixtures/two/package.json @@ -0,0 +1,30 @@ +{ + "name": "two", + "description": "The most interesting project in the world > Verb", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/two", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/two", + "bugs": { + "url": "https://github.com/jonschlinkert/two/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "generator.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "resolve-modules": "^0.1.2" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [] +} diff --git a/docs/_templates/api-config.md b/test/fixtures/two/templates/a.txt similarity index 100% rename from docs/_templates/api-config.md rename to test/fixtures/two/templates/a.txt diff --git a/test/fixtures/two/templates/b.txt b/test/fixtures/two/templates/b.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/two/templates/c.txt b/test/fixtures/two/templates/c.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/two/verbfile.js b/test/fixtures/two/verbfile.js new file mode 100644 index 00000000..04c031fc --- /dev/null +++ b/test/fixtures/two/verbfile.js @@ -0,0 +1,21 @@ +'use strict'; + +var Generate = require('../../..'); +var generate = new Generate(); + +generate.task('default', function() {}); +generate.task('a', function() {}); +generate.task('b', function() {}); +generate.task('c', function() {}); + +generate.register('foo', function(app) { + app.task('x', function() {}); + app.task('y', function() {}); + app.task('z', function() {}); +}); + +/** + * Expose this instance of `Generate` + */ + +module.exports = generate; diff --git a/test/generate.js b/test/generate.js new file mode 100644 index 00000000..7010c83e --- /dev/null +++ b/test/generate.js @@ -0,0 +1,35 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Generate = require('..'); +var generate; + +describe('generate', function() { + describe('cwd', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should get the current working directory', function() { + assert.equal(generate.cwd, process.cwd()); + }); + + it('should set the current working directory', function() { + generate.cwd = 'test/fixtures'; + assert.equal(generate.cwd, path.join(process.cwd(), 'test/fixtures')); + }); + }); + + describe('generator', function() { + beforeEach(function() { + generate = new Generate(); + }); + + it('should register the default generator', function() { + generate.register('default', require('./fixtures/def-gen')); + assert(generate.getGenerator('default')); + }); + }); +}); diff --git a/test/generators.env.js b/test/generators.env.js new file mode 100644 index 00000000..7c7dd97d --- /dev/null +++ b/test/generators.env.js @@ -0,0 +1,127 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var Base = require('..'); +var base; +var env; + +var fixtures = path.resolve.bind(path, __dirname + '/fixtures'); + +describe('env', function() { + describe('plugin', function() { + it('should work as a plugin', function() { + base = new Base(); + assert.equal(typeof base.createEnv, 'function'); + }); + }); + + describe('createEnv paths', function() { + beforeEach(function() { + base = new Base(); + }); + + describe('alias and function', function() { + it('should make the alias the exact name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.alias); + assert.equal(env.alias, 'foo-bar-baz'); + }); + + it('should not change the name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.name); + assert.equal(env.name, 'foo-bar-baz'); + }); + }); + + describe('alias and path', function() { + it('should set the env.name using the given name', function() { + var env = base.createEnv('foo', 'generate-foo/generator.js'); + assert.equal(env.name, 'foo'); + }); + + it('should not change the name when the second arg is a function', function() { + var fn = function() {}; + var env = base.createEnv('foo-bar-baz', fn); + assert(env); + assert(env.name); + assert.equal(env.name, 'foo-bar-baz'); + }); + }); + }); + + describe('createEnv', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should add an env object to the instance', function() { + var fn = function() {}; + env = base.createEnv('foo', fn); + assert(env); + }); + + it('should take options as the second arg', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert(env); + }); + + it('should prime `env` if it doesn\'t exist', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert(env); + }); + + it('should add an alias to the env object', function() { + var fn = function() {}; + env = base.createEnv('foo', {}, fn); + assert.equal(env.alias, 'foo'); + }); + + it('should not prefix the alias when a function is passed', function() { + var fn = function() {}; + delete base.prefix; + env = base.createEnv('foo', {}, fn); + assert.equal(env.name, 'foo'); + }); + + it('should not prefix a custom alias when a function is passed', function() { + var fn = function() {}; + base.prefix = 'whatever'; + env = base.createEnv('foo', {}, fn); + assert.equal(env.name, 'foo'); + }); + + it('should try to resolve an absolute path passed as the second arg', function() { + env = base.createEnv('foo', fixtures('generator.js')); + assert.equal(env.alias, 'foo'); + assert.equal(env.name, 'foo'); + }); + + it('should try to resolve a relative path passed as the second arg', function() { + env = base.createEnv('foo', 'generate-foo/generator.js'); + assert.equal(env.key, 'foo'); + assert.equal(env.alias, 'foo'); + assert.equal(env.name, 'foo'); + }); + + it('should throw an error when the path is not resolved', function(cb) { + try { + var env = base.createEnv('foo', fixtures('whatever.js')); + env.invoke(); + env.path; + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'cannot resolve: \'' + fixtures('whatever.js') + '\''); + cb(); + } + }); + }); +}); diff --git a/test/generators.events.js b/test/generators.events.js new file mode 100644 index 00000000..5777cb0c --- /dev/null +++ b/test/generators.events.js @@ -0,0 +1,161 @@ +'use strict'; + +require('mocha'); +var assert = require('assert'); +var Base = require('..'); +var base; + +describe('generators events', function() { + describe('generator', function() { + beforeEach(function() { + base = new Base(); + }); + + it('should emit generator when a generator is registered', function(cb) { + base = new Base(); + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + }); + + it('should emit generator when base.generators.get is called', function(cb) { + base = new Base(); + + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + base.getGenerator('foo'); + }); + + it('should emit generator.get when base.generators.get is called', function(cb) { + base = new Base(); + base.on('generator', function(generator) { + assert.equal(generator.alias, 'foo'); + cb(); + }); + + base.register('foo', function() {}); + base.getGenerator('foo'); + }); + + it('should emit error on base when a base generator emits an error', function(cb) { + base = new Base(); + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('foo', function(app) { + app.emit('error', new Error('whatever')); + }); + + base.getGenerator('foo'); + assert.equal(called, 1); + cb(); + }); + + it('should emit error on base when a base generator throws an error', function(cb) { + base = new Base(); + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('foo', function(app) { + app.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + + base.getGenerator('foo') + .build(function(err) { + assert(err); + assert.equal(called, 1); + cb(); + }); + + }); + + it('should emit errors on base from deeply nested generators', function(cb) { + base = new Base(); + var called = 0; + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('a', function() { + this.register('b', function() { + this.register('c', function() { + this.register('d', function() { + this.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + }); + }); + }); + + base.getGenerator('a.b.c.d') + .build(function(err) { + assert(err); + assert.equal(called, 1); + cb(); + }); + + }); + + it('should bubble up errors to all parent generators', function(cb) { + base = new Base(); + var called = 0; + + function count() { + called++; + } + + base.on('error', function(err) { + assert.equal(err.message, 'whatever'); + called++; + }); + + base.register('a', function() { + this.on('error', count); + + this.register('b', function() { + this.on('error', count); + + this.register('c', function() { + this.on('error', count); + + this.register('d', function() { + this.on('error', count); + + this.task('default', function(cb) { + cb(new Error('whatever')); + }); + }); + }); + }); + }); + + base.getGenerator('a.b.c.d') + .build(function(err) { + assert(err); + assert.equal(called, 6); + assert.equal(err.message, 'whatever'); + cb(); + }); + }); + }); +}); diff --git a/test/helpers.builtin.js b/test/helpers.builtin.js deleted file mode 100644 index ae6797b9..00000000 --- a/test/helpers.builtin.js +++ /dev/null @@ -1,155 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -/* deps: mocha lodash swig template */ -require('should'); -var Verb = require('..'); -var orig = process.cwd(); -var verb; - -describe('built-in helpers', function () { - before(function () { - process.chdir(__dirname + '/fixtures'); - }); - - after(function () { - process.chdir(orig); - }); - - beforeEach(function (cb) { - verb = new Verb.Verb(); - - verb.engine('*', require('engine-lodash')); - verb.option('cwd', __dirname + '/fixtures'); - verb.data({'__dirname': verb.option('cwd')}); - cb(); - }); - - describe('when automatically generated helpers are used:', function () { - it.only('should use them in templates:', function (cb) { - verb.helper('upper', function (str) { - return str.toUpperCase(); - }); - - verb.render('{%= upper(name) %}', {name: 'Jon Schlinkert'}, function (err, content) { - if (err) console.log(err); - content.should.equal('JON SCHLINKERT'); - cb(); - }); - }); - }); - - describe('apidocs helper:', function () { - it('should use the `apidocs` helper:', function (cb) { - var str = '{%= apidocs("apidocs-comments.js") %}'; - verb.render(str, function (err, content) { - if (err) console.log(err); - content.should.match(/apidocs-comments.js#L/i); - cb(); - }); - }); - }); - - describe('badge helper:', function () { - it('should use the `badge` helper:', function (cb) { - verb.render('{%= badge("travis") %}', function (err, content) { - if (err) console.log(err); - content.should.equal(' [![Build Status](https://travis-ci.org/verbose/verb.svg)](https://travis-ci.org/verbose/verb) '); - cb(); - }); - }); - - it('should use context pass to the helper:', function (cb) { - var str = '{%= badge("travis", {travis_url: "https://travis-ci.org/foo/bar"}) %}'; - var expected = ' [![Build Status](https://travis-ci.org/foo/bar.svg)](https://travis-ci.org/foo/bar) '; - - verb.render(str, function (err, content) { - if (err) console.log(err); - content.should.equal(expected); - cb(); - }); - }); - }); - - describe('include:', function () { - it('should use the `include` helper:', function (cb) { - verb.render('{%= include("tests") %}', function (err, content) { - if (err) console.log(err); - content.should.match(/Install dev dependencies:/i); - cb(); - }); - }); - - describe('escaping:', function () { - it('should not try to render escaped templates', function (cb) { - verb.page('foo.md', {content: '{%%= include("tests") %}'}); - verb.render('foo.md', function (err, content) { - if (err) console.log(err); - content.should.equal('{%= include("tests") %}'); - cb(); - }); - }); - }); - - it('should support using `includes` as deeply nested includes:', function (cb) { - verb.include('one', {content: '{%= include("a", {cwd: "auto-loading"}) %}'}); - verb.include('two', {content: 'c {%= include("one") %} d'}); - verb.include('three', {content: 'b {%= include("two") %} e'}); - verb.include('four', {content: 'a {%= include("three") %} f'}); - - verb.render('four', function (err, content) { - if (err) console.log(err); - content.should.match(/a b c # this is a fixture d e f/i); - cb(); - }); - }); - }); - - describe('docs:', function () { - it('should use the `docs` helper to get files without extension:', function (cb) { - var str = '{%= docs("README", {cwd: __dirname + "/templates"}) %}'; - verb.render(str, function (err, content) { - if (err) console.log(err); - content.should.match(/Success!/i); - cb(); - }); - }); - - it('should use the `docs` helper to get files with extension:', function (cb) { - var str = '{%= docs("README", {cwd: __dirname + "/templates"}) %}'; - verb.render(str, function (err, content) { - if (err) console.log(err); - content.should.match(/Success!/i); - cb(); - }); - }); - - it('should use the `docs` helper to get files with extension:', function (cb) { - var str = '{%= docs("README.md", {cwd: __dirname + "/templates"}) %}'; - verb.render(str, function (err, content) { - if (err) console.log(err); - content.should.match(/Success!/i); - cb(); - }); - }); - - it('should support using `doc` as deeply nested includes:', function (cb) { - verb.doc('one.md', {content: '{%= docs("a.md", {cwd: "auto-loading"}) %}'}); - verb.doc('two.md', {content: 'c {%= docs("one.md") %} d'}); - verb.doc('three.md', {content: 'b {%= docs("two.md") %} e'}); - verb.doc('four.md', {content: 'a {%= docs("three.md") %} f'}); - - verb.render('four', function (err, content) { - if (err) console.log(err); - content.should.match(/a b c # this is a fixture d e f/i); - cb(); - }); - }); - }); -}); diff --git a/test/helpers.collections.js b/test/helpers.collections.js deleted file mode 100644 index 0e6659bb..00000000 --- a/test/helpers.collections.js +++ /dev/null @@ -1,29 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('loaded helpers', function () { - it('should use helpers from the template-helpers library:', function (done) { - verb.render('{%= add(1, 2) %}', function (err, content) { - if (err) console.log(err); - content.should.equal('3'); - done(); - }); - }); - - it('should use helpers from the markdown-utils library:', function (done) { - verb.render('{%= mdu.link("a", "b") %}', function (err, content) { - if (err) console.log(err); - content.should.equal('[a](b)'); - done(); - }); - }); -}); diff --git a/test/helpers.user-defined.js b/test/helpers.user-defined.js deleted file mode 100644 index e4218480..00000000 --- a/test/helpers.user-defined.js +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('helpers', function () { - beforeEach(function (done) { - verb = new verb.Verb(); - verb.engine('*', require('engine-lodash')); - done(); - }); - - describe('when helpers are registered with the `.helper()` method:', function () { - it('should use them in templates:', function (done) { - verb.helper('upper', function (str) { - return str.toUpperCase(); - }); - verb.render('{%= upper(name) %}', {name: 'Jon Schlinkert'}, function (err, content) { - if (err) console.log(err); - content.should.equal('JON SCHLINKERT'); - done(); - }); - }); - }); -}); diff --git a/test/includes.js b/test/includes.js deleted file mode 100644 index 437d0ab7..00000000 --- a/test/includes.js +++ /dev/null @@ -1,122 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('includes', function () { - describe('when the author include is used:', function () { - beforeEach(function (done) { - verb = new verb.Verb(); - verb.engine('*', require('engine-lodash')); - done(); - }); - - it('should render only the name when `username` is not defined:', function (done) { - verb.render('{%= include("author") %}', function (err, content) { - if (err) console.log(err); - content.should.equal([ - '', - '**Jon Schlinkert**', - '' - ].join('\n')); - done(); - }); - }); - - it('should render github and twitter url when `username` is passed:', function (done) { - verb.render('{%= include("author", {username: "jonschlinkert"}) %}', function (err, content) { - if (err) console.log(err); - content.should.equal([ - '', - '**Jon Schlinkert**', - '', - ' + [github/jonschlinkert](https://github.com/jonschlinkert) ', - ' + [twitter/jonschlinkert](http://twitter.com/jonschlinkert) ', - '' - ].join('\n')); - done(); - }); - }); - - it('should render github url only when `author.username` is passed:', function (done) { - verb.render('{%= include("author", {author: {username: "jonschlinkert"}}) %}', function (err, content) { - if (err) console.log(err); - content.should.equal([ - '', - '**Jon Schlinkert**', - '', - ' + [github/jonschlinkert](https://github.com/jonschlinkert) ', - '' - ].join('\n')); - done(); - }); - }); - - it('should render twitter url only when `author.username` is passed:', function (done) { - verb.render('{%= include("author", {twitter: {username: "jonschlinkert"}}) %}', function (err, content) { - if (err) console.log(err); - content.should.equal([ - '', - '**Jon Schlinkert**', - ' + [twitter/jonschlinkert](http://twitter.com/jonschlinkert) ', - '' - ].join('\n')); - done(); - }); - }); - - it('should render github and twitter urls when `author.username` is passed:', function (done) { - verb.render('{%= include("author", {author: {username: "jonschlinkert"}}) %}', function (err, content) { - if (err) console.log(err); - content.should.equal([ - '', - '**Jon Schlinkert**', - '', - ' + [github/jonschlinkert](https://github.com/jonschlinkert) \n', - ].join('\n')); - done(); - }); - }); - - it('should work when `username` is on the context:', function (done) { - verb.data({username: 'jonschlinkert'}); - - verb.render('{%= include("author") %}', function (err, content) { - if (err) console.log(err); - content.should.equal([ - '', - '**Jon Schlinkert**', - '', - ' + [github/jonschlinkert](https://github.com/jonschlinkert) ', - ' + [twitter/jonschlinkert](http://twitter.com/jonschlinkert) ', - '' - ].join('\n')); - done(); - }); - }); - - it('should work when `username` is defined in front-matter:', function (done) { - verb.doc('foo.md', {content: '---\nusername: jonschlinkert\n---\n{%= include("author") %}'}); - verb.render('foo', function (err, content) { - console.log(verb.cache) - if (err) console.log(err); - content.should.equal([ - '', - '**Jon Schlinkert**', - '', - ' + [github/jonschlinkert](https://github.com/jonschlinkert) ', - ' + [twitter/jonschlinkert](http://twitter.com/jonschlinkert) ', - '' - ].join('\n')); - done(); - }); - }); - }); -}); diff --git a/test/middleware.js b/test/middleware.js deleted file mode 100644 index 8fb6e36b..00000000 --- a/test/middleware.js +++ /dev/null @@ -1,143 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('middleware', function () { - var orig = process.cwd(); - beforeEach(function (done) { - verb = new verb.Verb(); - before(function () { - process.chdir(__dirname + '/fixtures'); - }); - - after(function () { - process.chdir(orig); - }); - - verb.engine('md', require('engine-lodash')); - verb.option('cwd', __dirname); - verb.data({'__dirname': verb.option('cwd')}); - done(); - }); - - describe('todos:', function () { - it('should add a todos list to a markdown file\'s `todos` property:', function (done) { - verb.src('test/fixtures/middleware/todo.md') - .on('data', function (file) { - if (/\/todo\.md$/.test(file.path)) { - file.todos.should.be.an.array; - } - }) - .on('end', done); - }); - - it('should add a todos list to a javascript file\'s `todos` property:', function (done) { - verb.src('test/fixtures/middleware/todo.js') - .on('data', function (file) { - if (/\/todo\.js$/.test(file.path)) { - file.todos.should.be.an.array; - } - }) - .on('end', done); - }); - }); - - describe('multi-toc:', function () { - it('should generate a multi-file table of contents:', function (done) { - verb.doc('multi-toc.md', {cwd: __dirname + '/fixtures/middleware'}); - verb.views.docs.should.have.property('multi-toc'); - - verb.render('multi-toc', function (err, content) { - if (err) console.log(err); - content.should.match(/docs\/_verb\/[^\/]+\/#/); - done(); - }); - }); - - it('should strip the toc comment marker from the generated file:', function (done) { - verb.doc('multi-toc.md', {cwd: __dirname + '/fixtures/middleware'}); - verb.views.docs.should.have.property('multi-toc'); - - verb.render('multi-toc', function (err, content) { - if (err) console.log(err); - content.should.not.match(//); - done(); - }); - }); - - it('should generate a markdown table of contents for an `include`:', function (done) { - verb.include('*.md', {cwd: __dirname + '/fixtures/middleware'}); - verb.views.includes.should.have.property('toc'); - - verb.render('toc', function (err, content) { - if (err) console.log(err); - content.should.match(/<\!-- tocstop -->/); - done(); - }); - }); - - it('should add a toc to a file\'s `toc` property:', function (done) { - verb.src('test/fixtures/middleware/toc.md') - .on('data', function (file) { - if (/\/toc\.md$/.test(file.path)) { - console.log(file.toc) - file.toc.should.be.a.string; - } - }) - .on('end', done); - }); - - it('should generate a markdown table of contents for a `src` file:', function (done) { - verb.src('test/fixtures/middleware/toc.md') - .on('data', function (file) { - if (/\/toc\.md$/.test(file.path)) { - file.contents.toString().should.match(/<\!-- tocstop -->/); - } - }) - .on('end', done); - }); - }); - - describe('copyright:', function () { - it('should add copyright data to the context:', function (done) { - verb.src('test/fixtures/middleware/copyright.js') - .on('data', function (file) { - verb.cache.data.should.have.property('copyright'); - }) - .on('end', done); - }); - }); -}); diff --git a/test/plugin.todos.js b/test/plugin.todos.js deleted file mode 100644 index 1236c491..00000000 --- a/test/plugin.todos.js +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe.skip('default helpers:', function () { - var orig = process.cwd(); - - beforeEach(function (done) { - before(function () { - process.chdir(__dirname + '/fixtures'); - }); - - after(function () { - process.chdir(orig); - }); - - verb = new verb.Verb(); - verb.engine('*', require('engine-lodash')); - verb.option('cwd', __dirname); - verb.data({'__dirname': verb.option('cwd')}); - done(); - }); - - describe('todos:', function () { - it('should add todos to `file.todos` for markdown files:', function (done) { - verb.doc('abc.md', {cwd: __dirname + '/fixtures/todos'}); - verb.views.docs.should.have.property('abc'); - - verb.render('abc', function (err, content) { - if (err) console.log(err); - // verb.cache.data.should.have.property('todos'); - done(); - }); - }); - - // it('should generate a list of todos:', function (done) { - // verb.doc('abc.md', {cwd: __dirname + '/fixtures/todos'}); - // verb.doc('xyz.js', {cwd: __dirname + '/fixtures/todos'}); - // verb.views.docs.should.have.property('abc'); - // verb.views.docs.should.have.property('xyz'); - // console.log(verb.views.docs.xyz) - // }); - // stream.write(new File({ - // base: __dirname, - // path: __dirname + '/post.md' - // })); - - // stream.on('data', function (file) { - // buffer.push(file); - // }); - - // stream.on('end', function () { - // assert.equal(buffer.length, 1); - // assert.equal(buffer[0].relative, 'post.md'); - // cb(); - // }); - // stream.end(); - // it('should strip the toc marker from the generated file:', function (done) { - // verb.doc('todos.md', {cwd: __dirname + '/fixtures/middleware'}); - // verb.views.docs.should.have.property('todos'); - - // verb.render('todos', function (err, content) { - // if (err) console.log(err); - // content.should.not.match(//); - // done(); - // }); - // }); - }); -}); diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 00000000..9e662030 --- /dev/null +++ b/test/runner.js @@ -0,0 +1,110 @@ +'use strict'; + +require('mocha'); +var path = require('path'); +var assert = require('assert'); +var argv = require('yargs-parser')(process.argv.slice(2)); +var runner = require('base-runner'); +var Generate = require('..'); + +var fixtures = path.resolve.bind(path, __dirname, 'fixtures'); +var config = { + name: 'foo', + runner: require(fixtures('package.json')), + configName: 'generator', + extensions: { + '.js': null + } +}; + +describe('.runner', function() { + var error = console.error; + + beforeEach(function() { + console.error = function() {}; + }); + + afterEach(function() { + console.error = error; + }); + + describe('errors', function() { + it('should throw an error when a callback is not passed', function(cb) { + try { + runner(); + cb(new Error('expected an error')); + } catch (err) { + assert.equal(err.message, 'expected a callback function'); + cb(); + } + }); + + it('should error when an options object is not passed', function(cb) { + runner(Generate, {}, null, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the third argument to be an options object'); + cb(); + }); + }); + + it('should error when a liftoff config object is not passed', function(cb) { + runner(Generate, null, {}, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the second argument to be a liftoff config object'); + cb(); + }); + }); + + it('should error when a Generate constructor is not passed', function(cb) { + runner(null, {}, {}, function(err, app, runnerContext) { + assert(err); + assert.equal(err.message, 'expected the first argument to be a Base constructor'); + cb(); + }); + }); + }); + + describe('runner', function() { + it('should set "env" on app.cache.runnerContext', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert(app.cache.runnerContext.env); + assert.equal(typeof app.cache.runnerContext.env, 'object'); + cb(); + }); + }); + + it('should set "config" on app.cache.runnerContext', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert(app.cache.runnerContext.config); + assert.equal(typeof app.cache.runnerContext.config, 'object'); + cb(); + }); + }); + + it('should set the configFile on app.cache.runnerContext.env', function(cb) { + runner(Generate, config, argv, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cache.runnerContext.env.configFile, 'generator.js'); + cb(); + }); + }); + + it('should set cwd on the instance', function(cb) { + runner(Generate, config, {cwd: fixtures()}, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cwd, fixtures()); + cb(); + }); + }); + + it('should resolve configpath from app.cwd and app.configFile', function(cb) { + runner(Generate, config, {cwd: fixtures()}, function(err, app, runnerContext) { + if (err) return cb(err); + assert.equal(app.cache.runnerContext.env.configPath, path.resolve(__dirname, 'fixtures/generator.js')); + cb(); + }); + }); + }); +}); diff --git a/test/support/ignore.js b/test/support/ignore.js new file mode 100644 index 00000000..7dcbb755 --- /dev/null +++ b/test/support/ignore.js @@ -0,0 +1,6 @@ +module.exports = [ + 'addEventListener', + 'removeEventListener', + 'removeAllListeners', + 'removeListener' +]; diff --git a/test/support/index.js b/test/support/index.js new file mode 100644 index 00000000..e2194a5c --- /dev/null +++ b/test/support/index.js @@ -0,0 +1,64 @@ +'use strict'; + +var path = require('path'); +var loadpkg = require('load-pkg'); +var assert = require('assert'); +var ignore = require('./ignore'); +var cache = {}; + +exports.containEql = function containEql(actual, expected) { + if (Array.isArray(expected)) { + var len = expected.length; + while (len--) { + exports.containEql(actual[len], expected[len]); + } + } else { + for (var key in expected) { + assert.deepEqual(actual[key], expected[key]); + } + } +}; + +exports.keys = function keys(obj) { + var arr = []; + for (var key in obj) { + if (ignore.indexOf(key) === -1) { + arr.push(key); + } + } + return arr; +}; + +exports.resolve = function(filepath) { + filepath = filepath || ''; + var key = 'app:' + filepath; + if (cache.hasOwnProperty(key)) { + return cache[key]; + } + + var pkg = loadpkg.sync(process.cwd()); + var prefix = pkg.name !== 'templates' + ? 'templates' + : ''; + + var base = filepath + ? path.join(prefix, filepath) + : process.cwd(); + + var fp = tryResolve(base); + + if (typeof fp === 'undefined') { + throw new Error('cannot resolve: ' + fp); + } + return (cache[key] = require(fp)); +}; + +function tryResolve(name) { + try { + return require.resolve(name); + } catch (err) {} + + try { + return require.resolve(path.resolve(name)); + } catch (err) {} +} diff --git a/test/support/spy.js b/test/support/spy.js new file mode 100644 index 00000000..b473c6e1 --- /dev/null +++ b/test/support/spy.js @@ -0,0 +1,31 @@ +'use strict'; + +var fs = require('graceful-fs'); +var sinon = require('sinon'); + +var errorfn = false; + +function maybeCallAsync(module, func) { + var original = module[func]; + return sinon.stub(module, func, function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(module, func); + var err = typeof errorfn === 'function' && errorfn.apply(this, args); + if (!err) { + original.apply(this, arguments); + } else { + arguments[arguments.length - 1](err); + } + }); +} + +module.exports = { + setError: function(fn) { + errorfn = fn; + }, + chmodSpy: maybeCallAsync(fs, 'chmod'), + fchmodSpy: maybeCallAsync(fs, 'fchmod'), + futimesSpy: maybeCallAsync(fs, 'futimes'), + statSpy: maybeCallAsync(fs, 'stat'), + fstatSpy: maybeCallAsync(fs, 'fstat') +}; diff --git a/test/verb.create.js b/test/verb.create.js deleted file mode 100644 index ce26c48c..00000000 --- a/test/verb.create.js +++ /dev/null @@ -1,147 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('engine create:', function () { - beforeEach(function (done) { - verb = new verb.Verb(); - verb.engine('*', require('engine-lodash')); - done(); - }); - - describe('.create():', function () { - it('should create a new template `type`:', function () { - verb.create('include', 'includes'); - verb.should.have.properties('include', 'includes'); - }); - }); - - describe('when a new template type is created:', function () { - it('should add methods to the views for that type:', function () { - verb.create('apple', 'apples'); - verb.should.have.properties('apple', 'apples'); - }); - - it('should add templates to the views for a given template type:', function () { - verb.create('apple', 'apples'); - - verb.apple('a', 'one'); - verb.apple('b', 'two'); - verb.apple('c', 'three'); - - verb.views.should.have.property('apples'); - verb.views.apples.should.have.properties('a', 'b', 'c'); - }); - - describe('.decorate()', function () { - /* setup */ - beforeEach(function () { - verb = new verb.Verb(); - // create some custom template types - verb.create('block', 'blocks', { isLayout: true }); - verb.create('include', 'includes', { isPartial: true }); - verb.create('post', 'posts', { isRenderable: true }); - verb.create('doc', 'docs', { isRenderable: true }); - - // intentionally create dupes using different renderable types - verb.page('aaa.md', '<%= name %>', {name: 'Jon Schlinkert'}); - verb.post('aaa.md', '<%= name %>', {name: 'Brian Woodward'}); - verb.docs('aaa.md', '<%= name %>', {name: 'Halle Nicole'}); - - verb.include('sidebar.md', ''); - verb.block('default.md', 'abc {% body %} xyz'); - }); - - /* tests */ - it('should decorate the type with a `get` method:', function () { - verb.should.have.properties(['getPage', 'getPost', 'getDoc', 'getInclude']); - }); - - it('should decorate the type with a `render` method:', function () { - verb.should.have.properties(['renderPage', 'renderPost', 'renderDoc']); - }); - }); - }); - - describe('when the `isRenderable` flag is set on the options:', function () { - it('should push the name of the type into the `isRenderable` array:', function () { - verb.create('apple', 'apples', { isRenderable: true }); - - verb.type.renderable.should.containEql('pages'); - verb.type.renderable.should.containEql('apples'); - verb.type.renderable.should.containEql('apples'); - }); - }); - - describe('when the `isLayout` flag is set on the options:', function () { - it('should push the name of the type into the `isLayout` array:', function () { - verb.create('orange', 'oranges', { isLayout: true }); - - verb.type.layout.should.containEql('layouts'); - verb.type.layout.should.containEql('oranges'); - }); - }); - - describe('when no type flag is set on the options:', function () { - it('should push the name of the type into the `isPartial` array:', function () { - verb.create('banana', 'bananas'); - - verb.type.partial.should.containEql('partials'); - verb.type.partial.should.containEql('bananas'); - }); - }); - - describe('when the `isPartial` flag is set on the options:', function () { - it('should push the name of the type into the `isPartial` array:', function () { - verb.create('banana', 'bananas', { isPartial: true }); - - verb.type.partial.should.containEql('partials'); - verb.type.partial.should.containEql('bananas'); - }); - }); - - describe('when both the `isPartial` and the `isLayout` flags are set:', function () { - it('should push the type into both arrays:', function () { - verb.create('banana', 'bananas', { isPartial: true, isLayout: true }); - - verb.type.partial.should.containEql('bananas'); - verb.type.layout.should.containEql('bananas'); - }); - }); - - describe('when both the `isPartial` and the `isRenderable` flags are set:', function () { - it('should push the type into both arrays:', function () { - verb.create('banana', 'bananas', { isPartial: true, isRenderable: true }); - - verb.type.partial.should.containEql('bananas'); - verb.type.renderable.should.containEql('bananas'); - }); - }); - - describe('when both the `isLayout` and the `isRenderable` flags are set:', function () { - it('should push the type into both arrays:', function () { - verb.create('banana', 'bananas', { isLayout: true, isRenderable: true }); - - verb.type.layout.should.containEql('bananas'); - verb.type.renderable.should.containEql('bananas'); - }); - }); - - describe('when all three types flags are set:', function () { - it('should push the type into all three arrays:', function () { - verb.create('banana', 'bananas', { isPartial: true, isLayout: true, isRenderable: true }); - - verb.type.layout.should.containEql('bananas'); - verb.type.partial.should.containEql('bananas'); - verb.type.renderable.should.containEql('bananas'); - }); - }); -}); diff --git a/test/verb.cwd.js b/test/verb.cwd.js deleted file mode 100644 index afd5249d..00000000 --- a/test/verb.cwd.js +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('cwd', function () { - it('should set the cwd:', function () { - verb.option('cwd', __dirname); - verb.options.cwd.should.equal(__dirname); - }); -}); diff --git a/test/verb.data.js b/test/verb.data.js deleted file mode 100644 index 0b284759..00000000 --- a/test/verb.data.js +++ /dev/null @@ -1,147 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('verb.data()', function () { - beforeEach(function (done) { - verb = new verb.Verb(); - verb.engine('*', require('engine-lodash')); - verb.data({name: 'Halle'}); - verb.emit('loaded'); - done(); - }); - - describe('`.data()` method:', function () { - it('should store data on `verb.cache.data`:', function () { - verb.cache.data.name.should.equal('Halle'); - }); - - it('should pass be passed to templates as context:', function (done) { - verb.render('<%= name %>', function (err, content) { - if (err) console.log(err); - content.should.equal('Halle'); - done(); - }); - }); - - it('should be overridden by data passed on the render method:', function (done) { - verb.render('<%= name %>', {name: 'Brooke'}, function (err, content) { - if (err) console.log(err); - content.should.equal('Brooke'); - done(); - }); - }); - - it.only('should be overridden by data passed on template locals:', function (done) { - verb.data({name: 'Halle'}); - verb.doc('foo.md', {content: '<%= name %>', locals: {name: 'Brooke'}}); - - verb.render('foo', function (err, content) { - if (err) console.log(err); - // content.should.equal('Brooke'); - done(); - }); - }); - - it('should be overridden by data passed on template data:', function (done) { - verb.doc('foo.md', {content: '<%= name %>', data: {name: 'Brooke'}}); - - verb.render('foo', function (err, content) { - if (err) console.log(err); - content.should.equal('Brooke'); - done(); - }); - }); - - it('should be overridden by data passed on front-matter:', function (done) { - verb.doc('foo.md', {content: '---\nname: Brooke\n---\n{%= name %}'}); - verb.render('foo', function (err, content) { - if (err) console.log(err); - content.should.equal('Brooke'); - done(); - }); - }); - }); -}); - - -describe('verb data', function() { - beforeEach(function (done) { - verb = new verb.Verb(); - done(); - }); - - - describe('.data()', function() { - it('should set properties on the `data` object.', function() { - verb.set('data.foo', 'bar'); - verb.get('data').foo.should.equal('bar'); - verb.get('data.foo').should.equal('bar'); - }); - - it('should read files and merge data onto `cache.data`', function() { - verb.data('package.json', { namespace: false }); - verb.get('data.name').should.equal('verb'); - }); - - it('should read files and merge data onto `cache.data.package`', function() { - verb.data('package.json', { namespace: true }); - verb.get('data.package.name').should.equal('verb'); - }); - - it('should read files and merge data onto `cache.data`', function() { - verb.data({xyz: 'abc'}); - verb.get('data.xyz').should.equal('abc'); - }); - - it('should read files and merge data onto `cache.data`', function() { - verb.data([{aaa: 'bbb', ccc: 'ddd'}]); - verb.get('data.aaa').should.equal('bbb'); - verb.get('data.ccc').should.equal('ddd'); - }); - }); - - describe('.extendData()', function() { - it('should extend the `data` object.', function() { - verb.extendData({x: 'x', y: 'y', z: 'z'}); - verb.get('data').should.have.property('x'); - verb.get('data').should.have.property('y'); - verb.get('data').should.have.property('z'); - }); - }); - - describe('.flattenData()', function() { - it('should merge the value of a nested `data` property onto the root of the given object.', function() { - var root = verb.flattenData({data: {x: 'x'}, y: 'y', z: 'z'}); - root.should.have.property('x'); - root.should.have.property('y'); - root.should.have.property('z'); - root.should.not.have.property('data'); - }); - }); - - describe('.plasma()', function() { - it('should read JSON files and return an object.', function() { - var pkg = verb.plasma('package.json', { namespace: false }); - pkg.name.should.equal('verb'); - }); - - it('should expand a glob pattern, read JSON/YAML files and return an object.', function() { - var pkg = verb.plasma('p*.json', { namespace: false }); - pkg.name.should.equal('verb'); - }); - - it('should accept and object and return an object.', function() { - var foo = verb.plasma({a: 'b'}); - foo.a.should.equal('b'); - }); - }); -}); diff --git a/test/verb.defaults.js b/test/verb.defaults.js deleted file mode 100644 index 92205913..00000000 --- a/test/verb.defaults.js +++ /dev/null @@ -1,39 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -var orig = process.cwd(); -require('should'); - -describe('verb defaults', function () { - beforeEach(function (done) { - verb = new verb.Verb(); - done(); - }); - - before(function () { - process.chdir(__dirname + '/fixtures'); - }); - - after(function () { - process.chdir(orig); - }); - - describe('when an instance of verb is initialized:', function () { - it('should load the verb package.json onto the `verb` object:', function () { - verb.should.have.property('verb'); - verb.verb.should.have.property('name', 'verb'); - }); - - it('should load the user\'s package.json onto the `env` object:', function () { - verb.should.have.property('env'); - verb.env.should.have.property('name', 'foo'); - }); - }); -}); diff --git a/test/verb.js b/test/verb.js deleted file mode 100644 index ea69465f..00000000 --- a/test/verb.js +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('verb', function () { - it('should expose verb\'s package.json on `verb`:', function () { - verb.should.have.property('verb'); - verb.verb.should.be.an.object; - verb.verb.name.should.be.verb; // ;) - }); -}); diff --git a/test/verb.pkg.js b/test/verb.pkg.js deleted file mode 100644 index d9e73602..00000000 --- a/test/verb.pkg.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -require('should'); - -describe('package.json', function () { - it('should set a project\'s package.json on `verb.cache.data`:', function () { - verb.cache.data.name.should.equal('verb'); - }); -}); diff --git a/test/verb.render.js b/test/verb.render.js deleted file mode 100644 index 47269507..00000000 --- a/test/verb.render.js +++ /dev/null @@ -1,182 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var hbs = require('handlebars'); // keep -var consolidate = require('consolidate'); -var verb = require('..'); -require('should'); - -describe('verb.render()', function () { - beforeEach(function (done) { - verb = new verb.Verb(); - verb.engine('html', require('engine-lodash')); - done(); - }); - - describe('`this` object:', function () { - it('should expose `this` to the .render() method:', function (done) { - verb.render('<%= name %>', {name: 'Jon Schlinkert'}, function (err, content) { - if (err) console.log(err); - this.should.have.properties(['cache', 'options', 'engines', 'delims']); - done(); - }); - }); - }); - - describe.skip('when an un-cached string is passed to `.render()`:', function () { - it('should detect the engine from `ext` (with dot) on locals:', function (done) { - verb.render('<%= name %>', {name: 'Jon Schlinkert', engine: '.html'}, function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - - it('should detect the engine from `ext` (without dot) on locals:', function (done) { - verb.render('<%= name %>', {name: 'Jon Schlinkert', ext: 'html'}, function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - - it('should detect the engine from `engine` (with dot) on locals:', function (done) { - verb.render('<%= name %>', {name: 'Jon Schlinkert', engine: '.html'}, function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - - it('should detect the engine from `engine` (without dot) on locals:', function (done) { - verb.render('<%= name %>', {name: 'Jon Schlinkert', engine: 'html'}, function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - }); - - describe('when the name of a cached template is passed to `.render()`:', function () { - it('should find the template and render it:', function (done) { - verb.page('aaa.md', '{%= name %}', {name: 'Jon Schlinkert'}); - - verb.render('aaa.md', function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - - it('should render the first matching template if dupes are found:', function (done) { - verb.page('aaa.md', '{%= name %}', {name: 'Brian Woodward'}); - verb.create('post', 'posts', { isRenderable: true }); - verb.post('aaa.md', '{%= name %}', {name: 'Jon Schlinkert'}); - - verb.render('aaa.md', function (err, content) { - if (err) console.log(err); - content.should.equal('Brian Woodward'); - done(); - }); - }); - }); - - describe('engine render:', function () { - it('should determine the engine from the `path` on the given object:', function (done) { - var file = {path: 'a/b/c.md', content: '{%= name %}', locals: {name: 'Jon Schlinkert'}}; - - verb.render(file, function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - - it('should determine the engine from the `path` on the given object:', function (done) { - var file = {path: 'a/b/c.md', content: '{%= name %}'}; - - verb.render(file, {name: 'Jon Schlinkert'}, function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - - it('should render content with an engine from [consolidate].', function (done) { - verb.engine('hbs', consolidate.handlebars); - var hbs = verb.getEngine('hbs'); - - hbs.render('{{name}}', {name: 'Jon Schlinkert'}, function (err, content) { - if (err) console.log(err); - content.should.equal('Jon Schlinkert'); - done(); - }); - }); - - it('should use `file.path` to determine the correct consolidate engine to render content:', function (done) { - verb.engine('hbs', consolidate.handlebars); - verb.engine('swig', consolidate.swig); - verb.engine('tmpl', consolidate.lodash); - - var files = [ - {path: 'fixture.hbs', content: '{{title}}', locals: {title: 'Handlebars'}}, - {path: 'fixture.tmpl', content: '<%= title %>', locals: {title: 'Lo-Dash'}}, - {path: 'fixture.swig', content: '{{title}}', locals: {title: 'Swig'}} - ]; - - files.forEach(function(file) { - verb.render(file, function (err, content) { - if (err) console.log(err); - - if (file.path === 'fixture.hbs') { - content.should.equal('Handlebars'); - } - if (file.path === 'fixture.tmpl') { - content.should.equal('Lo-Dash'); - } - if (file.path === 'fixture.swig') { - content.should.equal('Swig'); - } - }); - }); - - done(); - }); - - it('should use the key of a cached template to determine the consolidate engine to use:', function (done) { - verb.engine('hbs', consolidate.handlebars); - verb.engine('swig', consolidate.swig); - verb.engine('tmpl', consolidate.lodash); - - verb.page('a.hbs', {content: '{{title}}', title: 'Handlebars'}); - verb.page('b.tmpl', {content: '<%= title %>', title: 'Lo-Dash'}); - verb.page('d.swig', {content: '{{title}}', title: 'Swig'}); - - Object.keys(verb.views.pages).forEach(function(file) { - verb.render(file, function (err, content) { - if (err) console.log(err); - - if (file.path === 'fixture.hbs') { - content.should.equal('Handlebars'); - } - if (file.path === 'fixture.tmpl') { - content.should.equal('Lo-Dash'); - } - if (file.path === 'fixture.swig') { - content.should.equal('Swig'); - } - }); - }); - - done(); - }); - }); -}); diff --git a/test/verb.variables.js b/test/verb.variables.js deleted file mode 100644 index 07fca5bd..00000000 --- a/test/verb.variables.js +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * verb - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -'use strict'; - -var verb = require('..'); -var orig = process.cwd(); -require('should'); - -describe('verb defaults', function () { - beforeEach(function (done) { - verb = new verb.Verb(); - done(); - }); - before(function () { - process.chdir(__dirname + '/fixtures'); - }); - after(function () { - process.chdir(orig); - }); - - describe('when an instance of verb is initialized:', function () { - it('should load the verb package.json onto the `verb` object:', function () { - verb.should.have.property('verb'); - verb.verb.should.have.property('name', 'verb'); - }); - - it('should load the user\'s package.json onto the `env` object:', function () { - verb.should.have.property('env'); - verb.env.should.have.property('name', 'foo'); - }); - }); -}); diff --git a/verbfile.js b/verbfile.js index 7015f040..0f8185f2 100644 --- a/verbfile.js +++ b/verbfile.js @@ -1,50 +1,29 @@ 'use strict'; -var gutil = require('gulp-util'); -var istanbul = require('gulp-istanbul'); -var jshint = require('gulp-jshint'); -var mocha = require('gulp-mocha'); -var through = require('through2'); -var verb = require('./'); +const through = require('through2'); +const reflinks = require('gulp-reflinks'); +const format = require('gulp-format-md'); +const utils = require('./lib/utils'); -verb.disable('debugEngine'); -verb.disable('reflinks'); +module.exports = function(app) { + app.use(require('verb-generate-readme')); -verb.task('readme', function () { - verb.src('.verb.md') - .pipe(verb.dest('.')) -}); + app.task('docs', ['setup'], function(cb) { + app.layouts('docs/src/templates/layouts/*.md'); + return app.src('docs/src/*.md', { layout: 'default' }) + .pipe(app.renderFile('*')) + .pipe(reflinks()) + .pipe(format()) + .pipe(app.dest('docs')); + }); -verb.task('docs', function () { - verb.src('docs/_templates/[e-g]*.md') - .pipe(verb.dest('test/actual')) -}); - -verb.task('lint', function () { - /* deps: jshint-stylish */ - verb.src(['index.js', 'lib/**/*.js']) - .on('error', gutil.log) - .pipe(jshint('.jshintrc')) - .pipe(jshint.reporter('jshint-stylish')); -}); - -verb.task('test', ['lint'], function (cb) { - verb.src(['index.js', 'lib/**/*.js']) - .on('error', gutil.log) - .pipe(istanbul()) - .pipe(istanbul.hookRequire()) - .on('finish', function () { - verb.src('test/*.js') - .pipe(mocha()) - .pipe(istanbul.writeReports({ - reporters: [ 'text' ], - reportOpts: {dir: 'coverage', file: 'summary.txt'} - })) - .on('end', function () { - verb.diff(); - cb(); - }); - }); -}); - -verb.task('default', ['readme']); + app.task('default', ['readme'], function() { + return app.src('README.md') + .pipe(through.obj(function(file, enc, next) { + file.content = file.content.replace(/^(#{2,}\s*\[)\./gm, '$1'); + next(null, file); + })) + .pipe(format()) + .pipe(app.dest('.')); + }); +};