Skip to content

Commit

Permalink
feat(generators): change how the settings object(s) defines available…
Browse files Browse the repository at this point in the history
… apis

  * Update & document `bat:api` sub-generator;
  * Fix doc refs;
  • Loading branch information
cueedee committed Jan 26, 2018
1 parent e9977e6 commit d4f395e
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 61 deletions.
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A Yeoman generator collection created by marviq.

Ever got tired of having to scaffold your new webapp projects over and over again? Moan no more; Yeoman and BAT will do it for you!


## Getting Started

### What is Yeoman?
Expand All @@ -23,6 +24,7 @@ Not every new computer comes with a Yeoman pre-installed. He lives in the [npm]
[sudo ]npm install -g yo
```


### Yeoman Generators

Yeoman travels light. He didn't pack any generators when he moved in. You can think of a generator like a plug-in. You get to choose what type of application you wish to create, such as a BAT webapp.
Expand All @@ -39,12 +41,13 @@ Finally, initiate the main app generator:
yo bat
```

Some of its subs:
Its subs:

```bash
yo bat:view
yo bat:model
yo bat:collection
yo bat:api
```

Or, if you want a head start, even:
Expand All @@ -53,6 +56,7 @@ Or, if you want a head start, even:
yo bat:demo
```


## The Finer Print

### Why BAT?
Expand Down Expand Up @@ -145,6 +149,16 @@ Furthermore, Yeoman can:
- Cause a singleton instance of the collection to be exported instead of the `class` itself;


#### Api

```shell
yo bat:api
```

An API is an instance of the BAT provided `ApiServicesCollection` which lives in the `src/apis/` section of your project map. Its purpose is, in essence, to
have a convenient way to organize the endpoint urls of a backend API's services relative to the common base-url defined for that API.


#### Demo

BAT also comes packed with a demo webapp implementation showcasing its features. To get this, either answer _yes_ to the relevant prompt from an initial `yo bat` run, or when you answered _no_ there earlier, invoke:
Expand Down Expand Up @@ -211,7 +225,8 @@ See [CONTRIBUTING](./CONTRIBUTING.md).

## Change Log

See [CHANGELOG](./CHANGELOG.md) for versions >`0.1.27`; For older versions, refer to the [releases on GitHub](https://github.com/marviq/generator-bat/releases?after=v1.0.0) for a detailed log of changes.
See [CHANGELOG](./CHANGELOG.md) for versions >`0.1.27`; For older versions, refer to the
[releases on GitHub](https://github.com/marviq/generator-bat/releases?after=v1.0.0) for a detailed log of changes.


## License
Expand Down
111 changes: 104 additions & 7 deletions generators/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ var Generator = require( 'yeoman-generator' )
, yosay = require( 'yosay' )
, youtil = require( './../../lib/youtil.js' )
, chalk = require( 'chalk' )
, glob = require( 'glob' )
, jsStringify = require( 'json-stable-stringify' )
, _ = require( 'lodash' )
;

Expand Down Expand Up @@ -65,13 +67,43 @@ class ApiGenerator extends Generator
{
this._assertBatApp();

// Find available Environment settings files:
//
var envs = this.envs
= {}
, base = this.destinationPath( 'settings' )
;

glob.sync( '**/*.json', { cwd: base } ).forEach(

( path ) =>
{
var pathAbs = base + '/' + path;
var match = this.fs.read( pathAbs ).match( /"environment"\s*:\s*"([^"]+)"/ );

if ( !( match ) ) { return; }

var name = match[ 1 ];

envs[ name ] =
{
name
, pathAbs
, path
}
;
}
);

// Container for template expansion data.
//
this.templateData = {};
}

prompting ()
{
var previous;

// Ask only those question that have not yet been provided with answers via the command line.
//
var prompts = this._promptsPruneByOptions(
Expand Down Expand Up @@ -99,17 +131,35 @@ class ApiGenerator extends Generator
, validate: youtil.isNonBlank
, filter: youtil.sentencify
}
, {
type: 'input'
, name: 'url'
, message: 'What is the base URL for this API? ' + chalk.gray( ' - please enter as code:' )
, default: youtil.definedToString( this.options.url )
, validate: youtil.isCoffeeScript
}
]
)
;

// Add per-environment prompts; each previous prompt's answer serves as the next one's default, using `this.options.url` as a base case.
//
prompts.push(

...Object.keys( this.envs ).map (

( envName ) =>
{
var prompt =
{
type: 'input'
, name: `env_${ envName }_url`
, message: 'What is the base URL for this API in the ' + chalk.green( envName ) + ' environment?'
, default: (( previous ) => ( answers ) => previous ? answers[ previous ] : youtil.definedToString( this.options.url )
)( previous )
, validate: youtil.isNonBlank
}

previous = prompt.name

return prompt
}
)
);

if ( prompts.length )
{
// Have Yeoman greet the user.
Expand Down Expand Up @@ -159,6 +209,53 @@ class ApiGenerator extends Generator
}
)();
}

install ()
{
// updateEnvironmentsSettings:
//
( () =>
{
var data = this.templateData
, fs = this.fs
, ts = 4
, align = 8 * ts
;

// Insert each environment's base `url` for this API.
//
Object.entries( this.envs ).forEach(
( [ envName, env ] ) =>
{
var envSettings = fs.readJSON( env.pathAbs )
, apis = envSettings.api
, url = data[ `env_${ envName }_url` ]
;

if ( !( apis ) || !( url ) ) { return; }

apis[ data.apiName ] = url;

// Keep entries sorted, comma-first style, aligned.
//
fs.write(
env.pathAbs
, jsStringify( envSettings, { space: ts } )
.replace(
/,$(\s*)\s{4}(?=\S)/gm
, ( match, space ) => space + ','.padEnd( ts )
)
.replace(
/(".*?")\s*:\s*(?![{\s[])/gm
, ( match, key ) => ( key += ': ' ).padEnd( key.length <= align ? align : ( key.length + ts - 1 - ( key.length - 1 ) % ts ) )
)
);
}
);

}
)();
}
}

_.extend(
Expand Down
9 changes: 7 additions & 2 deletions generators/api/templates/api.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
( ( factory ) ->
if typeof exports is 'object'
module.exports = factory(
require( 'madlib-settings' )

require( './../collections/api-services.coffee' )
)
else if typeof define is 'function' and define.amd
define( [
'madlib-settings'

'./../collections/api-services.coffee'
], factory )
return
)((
settings

ApiServicesCollection
) ->
Expand Down Expand Up @@ -40,14 +43,16 @@
###*
# The `<%- className %>`'s base url.
#
# Defined through the {{#crossLink 'SettingsEnvironmentModel/api:attribute'}}`environment.api.<%- apiName %>` setting{{/crossLink}}.
#
# @property url
# @type String
# @final
#
# @default <%- url %>
# @default settings.get( 'environment.api.<%- apiName %>' )
###

url: <%- url %>
url: settings.get( 'environment.api.<%- apiName %>' )

)

Expand Down
21 changes: 11 additions & 10 deletions generators/app/templates/settings/@README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Settings to be determined in such a file include:

setting | explanation
:--- | :---
`environment` | The target-environments name (production, acceptance, testing, staging, local, etc). Should really be identical to the settings file's name (excluding the `.json` extension).
`apiBaseUrl` | The base URL of the *default* API to use.
`environment` | The target-environment's name (production, acceptance, testing, staging, local, etc). Should really be identical to the settings file's name (excluding the `.json` extension).
`api` | A mappping of symbolic API names to their services' base URLs.
`locales` | A list of available locales.

Feel free to add further files to this directory as you see fit; for instance `<yourname>.js` for your personal local development configuration.
Expand All @@ -36,14 +36,15 @@ grunt --target=acceptance`

```json
{
"environment": "production"
, "apiBaseUrl": "/api"
, "locales":
{
"en-GB": "English"
, "en-US": "English (US)"
, "nl-NL": "Nederlands"
}
"api": {
"default": "/api"
},
"environment": "production",
"locales": {
"en-GB": "English",
"en-US": "English (US)",
"nl-NL": "Nederlands",
}
}
```

Expand Down
13 changes: 7 additions & 6 deletions generators/app/templates/settings/acceptance.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"apiBaseUrl": "/api"
, "environment": "acceptance"<% if ( i18n ) { %>
, "locales":
{
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
"api": {
"default": "/api"
}
, "environment": "acceptance"<% if ( i18n ) { %>
, "locales": {
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
}
13 changes: 7 additions & 6 deletions generators/app/templates/settings/local.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"apiBaseUrl": "/api"
, "environment": "local"<% if ( i18n ) { %>
, "locales":
{
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
"api": {
"default": "/api"
}
, "environment": "local"<% if ( i18n ) { %>
, "locales": {
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
}
13 changes: 7 additions & 6 deletions generators/app/templates/settings/production.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"apiBaseUrl": "/api"
, "environment": "production"<% if ( i18n ) { %>
, "locales":
{
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
"api": {
"default": "/api"
}
, "environment": "production"<% if ( i18n ) { %>
, "locales": {
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
}
13 changes: 7 additions & 6 deletions generators/app/templates/settings/testing.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"apiBaseUrl": "/api"
, "environment": "testing"<% if ( i18n ) { %>
, "locales":
{
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
"api": {
"default": "/api"
}
, "environment": "testing"<% if ( i18n ) { %>
, "locales": {
"<%- i18nLocaleDefault %>": "<%- i18nLocaleDefaultLanguage %> (<%- i18nLocaleDefaultRegion %>)"
}<% } %>
}
6 changes: 3 additions & 3 deletions generators/app/templates/src/apis/default.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@
###*
# The `DefaultApi`'s base url.
#
# Defined through the {{#crossLink 'Settings/environment.apiBaseUrl:property'}}`environment.apiBaseUrl` setting{{/crossLink}}.
# Defined through the {{#crossLink 'SettingsEnvironmentModel/api:attribute'}}`environment.api.default` setting{{/crossLink}}.
#
# @property url
# @type String
# @final
#
# @default `settings.get( 'environment.apiBaseUrl' )`
# @default settings.get( 'environment.api.default' )
###

url: settings.get( 'environment.apiBaseUrl' )
url: settings.get( 'environment.api.default' )

)

Expand Down
4 changes: 2 additions & 2 deletions generators/app/templates/src/apis/env.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
#
# These settings include:
#
# * `apiBaseUrl`
# * `api`
# * `environment`<% if ( i18n ) { %>
# * `locales`<% } %>
#
Expand Down Expand Up @@ -97,7 +97,7 @@
# @type String
# @final
#
# @default `settings.get( 'appBaseUrl' )`
# @default settings.get( 'appBaseUrl' )
###

url: settings.get( 'appBaseUrl' )
Expand Down
Loading

0 comments on commit d4f395e

Please sign in to comment.