blob: 2518917111756497af6d6e3c9d70f0b89e26223e [file] [log] [blame]
var shell = require('shelljs'),
fs = require('fs'),
url = require('url'),
plugins = require('./util/plugins'),
xml_helpers = require('./util/xml-helpers'),
metadata = require('./util/metadata'),
path = require('path'),
Q = require('q'),
registry = require('./registry/registry');
// XXX: leave the require('../plugman') because jasmine shits itself if you declare it up top
// possible options: link, subdir, git_ref, client, expected_id
// Returns a promise.
module.exports = function fetchPlugin(plugin_src, plugins_dir, options) {
// Ensure the containing directory exists.
shell.mkdir('-p', plugins_dir);
options = options || {};
options.subdir = options.subdir || '.';
options.searchpath = options.searchpath || [];
if ( typeof options.searchpath === 'string' ) {
options.searchpath = [ options.searchpath ];
}
// clone from git repository
var uri = url.parse(plugin_src);
// If the hash exists, it has the form from npm: http://foo.com/bar#git-ref[:subdir]
// NB: No leading or trailing slash on the subdir.
if (uri.hash) {
var result = uri.hash.match(/^#([^:]*)(?::\/?(.*?)\/?)?$/);
if (result) {
if (result[1])
options.git_ref = result[1];
if(result[2])
options.subdir = result[2];
// Recurse and exit with the new options and truncated URL.
var new_dir = plugin_src.substring(0, plugin_src.indexOf('#'));
return fetchPlugin(new_dir, plugins_dir, options);
}
}
// If it looks like a network URL, git clone it.
if ( uri.protocol && uri.protocol != 'file:' && !plugin_src.match(/^\w+:\\/)) {
require('../plugman').emit('log', 'Fetching plugin "' + plugin_src + '" via git clone');
if (options.link) {
return Q.reject(new Error('--link is not supported for git URLs'));
} else {
var data = {
source: {
type: 'git',
url: plugin_src,
subdir: options.subdir,
ref: options.git_ref
}
};
return plugins.clonePluginGitRepo(plugin_src, plugins_dir, options.subdir, options.git_ref)
.then(function(dir) {
return checkID(options.expected_id, dir);
})
.then(function(dir) {
metadata.save_fetch_metadata(dir, data);
return dir;
});
}
} else {
// If it's not a network URL, it's either a local path or a plugin ID.
// NOTE: Can't use uri.href here as it will convert spaces to %20 and make path invalid.
// Use original plugin_src value instead.
var p, // The Q promise to be returned.
linkable = true,
plugin_dir = path.join(plugin_src, options.subdir);
if (fs.existsSync(plugin_dir)) {
p = Q(plugin_dir);
} else {
// If there is no such local path, it's a plugin id.
// First look for it in the local search path (if provided).
var local_dir = findLocalPlugin(plugin_src, options.searchpath);
if (local_dir) {
p = Q(local_dir);
require('../plugman').emit('log', 'Found plugin "' + plugin_src + '" at: ' + local_dir);
} else {
// If not found in local search path, fetch from the registry.
linkable = false;
require('../plugman').emit('log', 'Fetching plugin "' + plugin_src + '" via plugin registry');
p = registry.fetch([plugin_src], options.client);
}
}
return p
.then(function(dir) {
return copyPlugin(dir, plugins_dir, options.link && linkable);
})
.then(function(dir) {
return checkID(options.expected_id, dir);
});
}
};
function readId(dir) {
var xml_path = path.join(dir, 'plugin.xml');
require('../plugman').emit('verbose', 'Fetch is reading plugin.xml from location "' + xml_path + '"...');
var et = xml_helpers.parseElementtreeSync(path.join(dir, 'plugin.xml'));
var plugin_id = et.getroot().attrib.id;
return plugin_id;
}
// Helper function for checking expected plugin IDs against reality.
function checkID(expected_id, dir) {
if ( expected_id ) {
var id = readId(dir);
if (expected_id != id) {
throw new Error('Expected fetched plugin to have ID "' + expected_id + '" but got "' + id + '".');
}
}
return dir;
}
// Look for plugin in local search path.
function findLocalPlugin(plugin_id, searchpath) {
for(var i = 0; i < searchpath.length; i++){
var files = fs.readdirSync(searchpath[i]);
for (var j = 0; j < files.length; j++){
var plugin_path = path.join(searchpath[i], files[j]);
try {
var id = readId(plugin_path);
if (plugin_id === id) {
return plugin_path;
}
}
catch(err) {
require('../plugman').emit('verbose', 'Error while trying to read plugin.xml from ' + plugin_path);
}
}
}
return null;
}
// Copy or link a plugin from plugin_dir to plugins_dir/plugin_id.
function copyPlugin(plugin_dir, plugins_dir, link) {
var plugin_id = readId(plugin_dir);
var dest = path.join(plugins_dir, plugin_id);
shell.rm('-rf', dest);
if (link) {
require('../plugman').emit('verbose', 'Symlinking from location "' + plugin_dir + '" to location "' + dest + '"');
fs.symlinkSync(plugin_dir, dest, 'dir');
} else {
shell.mkdir('-p', dest);
require('../plugman').emit('verbose', 'Copying from location "' + plugin_dir + '" to location "' + dest + '"');
shell.cp('-R', path.join(plugin_dir, '*') , dest);
}
var data = {
source: {
type: 'local',
path: plugin_dir
}
};
metadata.save_fetch_metadata(dest, data);
return dest;
}