bowerから必要なファイルだけとってきて必要な場所に配置するようにする

## bower installするとなんかいろいろ落ちてくる。


ある時思った。HTMLページ書くのでjQueryでも使うか。
でもダウンロードするのめんどくさい。そうだ、[bower](http://bower.io/)がある。

```bash
$ bower install jquery
```

で一発だぜ。

さて、HTMLからjQuery読み込むか。

あれ、どこだ？

```
bower_components
    └─ jquery
        ├─ dist
        |   └─ ... 
        ├─ src
        |   └─ ... 
        ├─  bower.json
        ├─ .bower.json
        ├─ bower.json
        └─ MIT-LICENSE.txt
```

`bower`でインストールすると、パッケージのすべてのファイルがまとめて`bower_components`ディレクトリしたに配置される。
`jquery.js`が欲しければ`bower_components/jquery/dist/jquery.js`を自分で探さなければならない。

やだなにこれうざい。`jquery.js`が欲しいだけなのに。


## 必要なファイルだけさくっとダウンロードして配置したい。

きちんと管理するならbower.jsonにあれこれ書いてversion指定して、云々となるけどあんまりそんなことしたくなかったりする。

パッケージ情報なら`package.json`に書いてあるし。`bower.json` (`component.json`) に同じようなこと書きたくないし。

サクッと必要なファイルだけ拾ってきて必要な場所に置けるようにしたい。

なんかそんな裏技ないかと[bowerのAPIページ](http://bower.io/docs/api/)をしばし眺める。

ない。

全部見たわけじゃやないけど多分ない。気がする。

でも代わりに[Programmatic APIが公開されている](http://bower.io/docs/api/#programmatic-api)のを見つけた。

じゃあ作るか。


## 必要なファイルだけさくっとダウンロードして配置する簡易コマンドを設計する。

パッケージ名と配置先を指定すると、勝手にダウンロードして置いてくれる感じにしたい。
これを`handybower`と名付けよう。

使い方は、

```bash
# jqueryの最新版と、angularのv1.4.3を"app/public/vendor"ディレクトリに配置する
$ handybower jquery angular#1.4.3 -d app/public/vendor
```

的な。

ついでにjsからも、

```javascript
#!/usr/bin/env node

var handybower = require('handybower');

handybower([
    "jquery",
    "angular#1.4.3"
], {
    dest: "app/public/vendor"
}, function (err) {

});
```

的な感じで呼べるとなお素敵。

## 必要なファイルだけさくっとダウンロードして配置する簡易コマンドを実装する

[bowerのProgrammatic API](http://bower.io/docs/api/#programmatic-api)のページを見ると、
こんな感じでインストールができるようだ。

```javascript
bower.commands
    .install(['jquery'], { save: true }, { /* custom config */ })
    .on('end', function (installed) {
        console.log(installed);
    });
```

インストールされたパッケージの直下には`bower.json`がいて、その中の`main`属性に
外に公開したいファイルが書かれている。

よし、これを利用しよう。

まずはこんな感じで、jsの関数を用意する。

```javascript
/**
 * Install bower components and collect main files
 * @function handyBower
 * @param {string|string[]} name - Module name to install.
 * @param {object} options - Optional settings.
 * @param {string} [options.dest='handybower_components'] - Destination directory path
 * @param {boolean} [options.verbose=false] - Log verbose.
 * @param {string[]} [options.main] - Name of main files. Default is `main` attribuite in installed bower.json.
 * @param {function} callback - Callback when done.
 */

"use strict";


var argx = require('argx'),
    writeout = require('writeout'),
    fs = require('fs'),
    path = require('path'),
    glob = require('glob'),
    async = require('async'),
    bower = require('bower'),
    Colorprint = require('colorprint/lib/colorprint');

function handyBower(names, options, callback) {
    var args = argx(arguments);
    callback = args.pop('function') || argx.noop;
    options = args.pop('object');
    names = args.remain().reduce(_concat, []).map(function (name) {
        if (typeof(name) === 'object') {
            var data = name;
            return Object.keys(data).map(function (name) {
                return [name, data[name]].join('#');
            });
        } else {
            return String(name);
        }
    }).reduce(_concat, []);

    var logger = new Colorprint({
        PREFIX: '[handybower] '
    });

    var verbose = !!options.verbose,
        main = [].concat(options.main || '')
            .reduce(_concat, [])
            .filter(_notEmpty);

    if (verbose) {
        logger.trace('options:\n', options);
    }

    var destDir = options.dest || 'handybower_components';
    if (!destDir) {
        callback(new Error('dest is required.'));
        return;
    }

    var aborted = false;

    var components = {};
    logger.info('Installing started...');
    bower.commands
        .install(names, {
            force: true
        }, {
            interactive: false
        })
        .on('error', function (err) {
            callback(err);
            aborted = true;
        })
        .on('log', function (packet) {
            var data = packet.data || {};
            var endpoint = data.endpoint || {};
            var name = endpoint.name;
            components[name] = components[name] || data.canonicalDir;
        })
        .on('end', function (installed) {
            if (aborted) {
                return;
            }
            Object.keys(installed).forEach(function (name) {
                components[name] = components[name] || installed[name]['canonicalDir'];
            });
            var dirnames = Object.keys(components).map(function (name) {
                return components[name];
            });
            async.eachSeries(dirnames, function (dirname, callback) {
                var data = require(path.resolve(dirname, 'bower.json'));
                var found = [].concat(main && main.length ? main : data.main).map(function (pattern) {
                    return glob.sync(pattern, {cwd: dirname});
                }).reduce(_concat, []);
                if (verbose) {
                    logger.trace('main files:\n%s', found);
                }
                async.eachSeries(found, function (filename, callback) {
                    var src = path.resolve(dirname, filename),
                        dest = path.resolve(destDir, path.basename(filename));

                    async.waterfall([
                        function (callback) {
                            fs.readFile(src, callback);
                        },
                        function (content, callback) {
                            writeout(dest, String(content), {
                                mkdirp: true
                            }, callback);
                        },
                        function (result, callback) {
                            if (!result.skipped) {
                                logger.debug('File generated:', dest);
                            }
                            callback(null);
                        }
                    ], callback);
                }, callback);
            }, function (err) {
                if (err) {
                    console.error(err);
                    logger.error('...installing failed!');
                } else {
                    logger.info('...installing done!');
                }
                callback(err);
            });
        });
}

function _concat(a, b) {
    return a.concat(b);
}
function _notEmpty(val) {
    return !!val;
}
module.exports = handyBower;
```

次にこれを、[commander](https://www.npmjs.com/package/commander)を使ってCLIから呼べるようにする。


```javascript
#!/usr/bin/env node

/**
 * @file Bins script for handybower.
 */

"use strict";


var program = require('commander'),
    pkg = require('../package'),
    handybower = require('../handybower');


program
    .usage('[options] [name ...]')
    .version(pkg['version'])
    .option('-d, --dest <dest>', "Destination directory path.")
    .option('-v, --verbose', "Show verbose log.")
    .parse(process.argv);


var moduleNames = program.args;

handybower(moduleNames, {
    verbose: program.verbose,
    dest: program.dest,
    main: program.main && (program.main).split(/,/g)
});
```

よしできた。

## 実装したコマンドをnpmレポジトリで公開する

あとは[package.json](https://github.com/okunishinishi/node-handybower/blob/master/package.json)をちゃちゃっと整理してテストを書いてそのまま[npmモジュールとして公開](https://www.npmjs.com/package/handybower)


+ [handybower](https://www.npmjs.com/package/handybower)



```bash
$ npm install handybower -g
```

これでちょろっと使うのが楽になる。