├── .gitignore ├── config └── default.sample.js ├── README.md ├── lib ├── utils.js ├── defaults.js ├── parser │ ├── optionsParser.js │ ├── resourceParser.js │ └── dataParser.js ├── stringify.js └── actions.js ├── package.json ├── LICENSE ├── usage.txt └── bin └── cli.js /.gitignore: -------------------------------------------------------------------------------- 1 | config/*.js 2 | !config/default.sample.js 3 | node_modules 4 | .idea -------------------------------------------------------------------------------- /config/default.sample.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | "url": "URL to your gitlab", 3 | "username": "your username", 4 | "privateToken": "Your private token" 5 | } 6 | 7 | module.exports = config; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gitlab-CLI 2 | 3 | This node.js application provides a command line interface to the GitLab API v3. 4 | 5 | ## Installation 6 | 7 | Install it using npm: 8 | 9 | npm install gitlab-cli -g 10 | 11 | ## Usage 12 | 13 | gitlab Action [Resource] [Options] 14 | 15 | To get a list of available commands 16 | 17 | gitlab --help 18 | 19 | 20 | See [usage.txt](usage.txt) for more info -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | function split(string, delimiter, limit) 2 | { 3 | var _parts = []; 4 | var parts = string.split(delimiter); 5 | 6 | for(var i = 0; i < parts.length; i++) { 7 | if (i < limit) { 8 | _parts.push(parts[i]); 9 | } 10 | else { 11 | _parts[_parts.length - 1] += parts[i]; 12 | } 13 | 14 | if (i >= limit - 1 && i < parts.length - 1) { 15 | _parts[_parts.length - 1] += delimiter; 16 | } 17 | } 18 | 19 | return _parts; 20 | } 21 | module.exports.split = split; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitlab-cli", 3 | "version": "0.1.0", 4 | "description": "Gitlab API on the command line", 5 | "main": "./bin/cli.js", 6 | "bin": { 7 | "gitlab": "./bin/cli.js" 8 | }, 9 | "author": { 10 | "name": "Ondrej Brinkel", 11 | "email": "info@anzui.de" 12 | }, 13 | "preferGlobal": true, 14 | "homepage": "https://github.com/der-On/gitlab-cli", 15 | "license": "BSD", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/der-On/gitlab-cli.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/der-On/gitlab-cli/issues" 22 | }, 23 | "keywords": [ 24 | "gitlab", 25 | "cli", 26 | "command line" 27 | ], 28 | "readmeFilename": "README.md", 29 | "dependencies": { 30 | "home": "^0.1.3", 31 | "node-gitlab": ">=0.0.6", 32 | "optimist": ">=0.6.0" 33 | }, 34 | "engines": { 35 | "node": "*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | function resource(data) 2 | { 3 | if (typeof data !== 'object') { 4 | data = {}; 5 | } 6 | 7 | return { 8 | namespace: data.namespace || null, 9 | project: data.project || null, 10 | type: data.type || null, 11 | id: data.id || null 12 | }; 13 | } 14 | module.exports.resource = resource; 15 | 16 | function options(data) 17 | { 18 | if (typeof data !== 'object') { 19 | data = {}; 20 | } 21 | 22 | return { 23 | username: data.username || "me", 24 | filters: data.filters || null, 25 | json: data.json || false, 26 | data: data.data || null, 27 | dataJson: data.dataJson || null, 28 | debug: false 29 | }; 30 | } 31 | module.exports.options = options; 32 | 33 | function data(data) 34 | { 35 | if (typeof data !== 'object') { 36 | data = {}; 37 | } 38 | 39 | var _data = { 40 | title: data.title || null, 41 | description: data.description || null, 42 | labels: data.labels || null 43 | }; 44 | 45 | for(var key in data) { 46 | _data[key] = data[key]; 47 | } 48 | 49 | return data; 50 | } 51 | module.exports.data = data; -------------------------------------------------------------------------------- /lib/parser/optionsParser.js: -------------------------------------------------------------------------------- 1 | var defaults = require('../defaults'); 2 | var split = require('../utils').split; 3 | 4 | function parse(argv) 5 | { 6 | var options = defaults.options(); 7 | 8 | var _filters = null; 9 | if (argv['f'] && argv.f.trim() !== '') { 10 | _filters = argv.f.trim(); 11 | } 12 | if (argv['filters'] && argv.filters.trim() !== '') { 13 | _filters = argv.filters.trim(); 14 | } 15 | 16 | if (_filters) { 17 | options.filters = {}; 18 | 19 | _filters = _filters.split(','); 20 | _filters.forEach(function(_filter, i) { 21 | _filters[i] = split(_filter, '=', 2); 22 | 23 | if (_filters[i].length < 2) { 24 | _filters[i].push(null); 25 | } 26 | 27 | options.filters[_filters[i][0]] = _filters[i][1]; 28 | }); 29 | } 30 | 31 | if (argv['json']) { 32 | options.json = true; 33 | } 34 | 35 | if (argv['debug']) { 36 | options.debug = true; 37 | } 38 | 39 | if (argv['u'] || argv['user']) { 40 | options.username = argv.u || argv.user; 41 | } 42 | 43 | if (argv['accept-certs']) { 44 | options.acceptCerts = true; 45 | } 46 | 47 | if (argv['config']) { 48 | options.config = argv['config']; 49 | } 50 | else { 51 | options.config = '~/.config/gitlab-cli/default.js' 52 | } 53 | 54 | return options; 55 | } 56 | module.exports = parse; -------------------------------------------------------------------------------- /lib/parser/resourceParser.js: -------------------------------------------------------------------------------- 1 | var defaults = require('../defaults'); 2 | var split = require('../utils').split; 3 | 4 | function parse(argv, aliases, data) 5 | { 6 | var resource = defaults.resource(); 7 | var _resource = argv._[1] || ''; 8 | _resource.trim(); 9 | 10 | // resolve aliases 11 | for(var alias in aliases) { 12 | if (_resource.indexOf(alias + '/') === 0) { 13 | _resource = _resource.replace(alias, aliases[alias]); 14 | continue; 15 | } 16 | } 17 | 18 | _resource = split(_resource, '/', 4); 19 | 20 | 21 | 22 | if (_resource.length > 1) { 23 | _resource.forEach(function(part, i) { 24 | if (part.trim() === '') { 25 | console.error('invalid resource path'); 26 | process.exit(1); 27 | } 28 | }); 29 | } 30 | 31 | // normalize resource 32 | resource.namespace = data.namespace ? data.namespace : (_resource.length >= 2) ? _resource[0] : null; 33 | resource.project = data.project ? data.project : (_resource.length >= 3) ? _resource[1] : null; 34 | resource.type = data.type ? data.type : (_resource.length >= 3) ? 35 | _resource[2] : (_resource.length === 1) ? _resource[0] : _resource[1] 36 | ; 37 | resource.id = data.id ? data.id : _resource[3] || null; 38 | 39 | if (resource.type.length > 0 && resource.type.substr(-1,1) !== 's') { 40 | resource.type += 's'; 41 | } 42 | 43 | return resource; 44 | } 45 | module.exports = parse; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Ondrej 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /usage.txt: -------------------------------------------------------------------------------- 1 | Usage: gitlab Action [Resource] [Options] 2 | 3 | Resource: 4 | Is the relative url to the ressource. 5 | 6 | Examples: 7 | foo/bar/issue for issues in the namespace 'foo' and the project 'bar' 8 | foo/bar/issue/12 for issue with id 12 in namespace 'foo' and the project 'bar' 9 | 10 | Actions: 11 | get RESOURCE Gets resource 12 | list RESOURCE Lists resource 13 | create RESOURCE Creates resource 14 | remove RESOURCE Deletes resource 15 | update RESOURCE Updates existing resource 16 | close RESOURCE Closes an issue 17 | open RESOURCE Opens an issue 18 | comment RESOURCE Comments an issue 19 | alias RESOURCE ALIAS Create a resource alias to prevent retyping long resource paths 20 | rm-alias ALIAS Removes an existing alias 21 | list-aliases Lists all aliases 22 | 23 | Options: 24 | -h --help Display this help 25 | -m 'MESSAGE' Adds a message (first line will be treated as title other lines as body) 26 | -u --user USERNAME User to assign the issue to, default is "me" (use "me" to assign to user defined in environment) 27 | -l --labels tag1,tag2 Comma separated list of labels 28 | -f --filters key=value,... Filter results by comma separated list of key=value pairs. Results must match these key=value pairs. 29 | --json Output all data as JSON 30 | --data key=value,... Set data by comma separated list of key=value pairs. 31 | --data-json JSON Set data using a JSON string. If "data" is also set, it will overwrite existing keys. 32 | --accept-certs If present all SSL certificates (including self-signed) will be accepted 33 | --config=path/to/config.js Set path to configuration file to use. If not set it will look for the file in ~/.config/gitlab-cli/default.js 34 | --debug If present will display debug information -------------------------------------------------------------------------------- /lib/stringify.js: -------------------------------------------------------------------------------- 1 | function issues(issue) 2 | { 3 | var out = '#' + issue.id; 4 | if (issue.state) { 5 | out += ' ('+ issue.state + ')'; 6 | } 7 | 8 | out += ': ' + issue.title; 9 | 10 | if (issue.description) { 11 | out += '\n' + issue.description; 12 | } 13 | 14 | if (issue.labels && issue.labels.length > 0) { 15 | out += '\n\nLabels: ' + issue.labels.join(', '); 16 | } 17 | return out; 18 | } 19 | module.exports.issues = issues; 20 | 21 | 22 | function milestones(milestone) 23 | { 24 | var out = '#' + milestone.id; 25 | if (milestone.state) { 26 | out += ' ('+ milestone.state + ')'; 27 | } 28 | 29 | out += ': ' + milestone.title; 30 | 31 | if (milestone.due_date) { 32 | out += ' (' + milestone.due_date + ')'; 33 | } 34 | 35 | if (milestone.description) { 36 | out += '\n' + milestone.description; 37 | } 38 | return out; 39 | } 40 | module.exports.milestones = milestones; 41 | 42 | 43 | function users(user) 44 | { 45 | var out = '#' + user.id; 46 | if (user.state) { 47 | out += ' ('+ user.state + ')'; 48 | } 49 | 50 | out += ': ' + user.username + ' | ' + user.email; 51 | 52 | out += '\n'; 53 | 54 | out += 'Role: '; 55 | switch (user.access_level) { 56 | case 10: out += 'Guest'; break; 57 | case 20: out += 'Reporter'; break; 58 | case 30: out += 'Developer'; break; 59 | case 40: out += 'Master'; break; 60 | } 61 | 62 | 63 | if (user.name) { 64 | out += ' | Name: ' + user.name; 65 | } 66 | 67 | return out; 68 | } 69 | module.exports.users = users; 70 | module.exports.members = users; 71 | 72 | 73 | function projects(project) 74 | { 75 | var out = '#' + project.id; 76 | 77 | 78 | out += ' (' + ((project.public) ? 'public' : 'private') + ')'; 79 | 80 | out += ': ' + project.name; 81 | 82 | if (project.description) { 83 | out += '\n' + project.description; 84 | } 85 | out += '\n' + 'path: ' + project.path_with_namespace; 86 | out += '\n' + 'ssh: ' + project.ssh_url_to_repo; 87 | out += '\n' + 'http: ' + project.http_url_to_repo; 88 | out += '\n' + 'web: ' + project.web_url; 89 | 90 | return out; 91 | } 92 | module.exports.projects = projects; 93 | 94 | function hooks(hook) 95 | { 96 | var out = '#' + hook.id; 97 | 98 | var date = new Date(hook.created_at); 99 | out += ' (' + date + ')'; 100 | out += ': ' + hook.url; 101 | 102 | return out; 103 | } 104 | module.exports.hooks = hooks; -------------------------------------------------------------------------------- /lib/parser/dataParser.js: -------------------------------------------------------------------------------- 1 | var defaults = require('../defaults'); 2 | var split = require('../utils').split; 3 | 4 | function parseMessage(argv, data) 5 | { 6 | if (argv['m']) { 7 | var message = split(argv.m, "\n", 2); 8 | 9 | if (message.length >= 1) { 10 | data.title = message[0].trim(); 11 | } 12 | if (message.length === 2) { 13 | data.description = message[1].trim(); 14 | } 15 | } 16 | 17 | return data; 18 | } 19 | 20 | function parseLabels(argv, data) 21 | { 22 | var labels = []; 23 | 24 | if (argv['l'] && argv.l.trim() !== '') { 25 | labels = argv.l.trim().split(','); 26 | } 27 | else if (argv['labels'] && argv.labels.trim() !== '') { 28 | labels = argv.labels.trim().split(','); 29 | } 30 | 31 | if (labels.length > 0) { 32 | data.labels = []; 33 | 34 | labels.forEach(function(label, i) { 35 | data.labels.push(label.trim()); 36 | }); 37 | } 38 | 39 | return data; 40 | } 41 | 42 | function parseData(argv, data) 43 | { 44 | if (argv['data']) { 45 | var _data = argv['data'].trim(); 46 | _data = _data.split(','); 47 | 48 | _data.forEach(function(__data, i) { 49 | _data[i] = split( __data, '=', 2); 50 | 51 | if (_data[i].length < 2) { 52 | _data[i].push(null); 53 | } 54 | 55 | data[_data[i][0]] = _data[i][1]; 56 | }); 57 | } 58 | 59 | return data; 60 | } 61 | 62 | function parseDataJson(argv, data) 63 | { 64 | if (argv['data-json']) { 65 | var _data = argv['data-json'].trim(); 66 | 67 | try { 68 | JSON.parse(_data); 69 | 70 | for(var key in _data) { 71 | data[key] = _data[key]; 72 | } 73 | } 74 | catch (error) { 75 | 76 | } 77 | } 78 | 79 | return data; 80 | } 81 | 82 | function parseUser(argv, data) 83 | { 84 | if (typeof(argv['u']) === 'string' && argv.u.trim() !== '') { 85 | data.username = argv.u.trim(); 86 | } 87 | else if(typeof(argv['user']) === 'string' && argv.user.trim() !== '') { 88 | data.username = argv.user.trim(); 89 | } 90 | 91 | if (data.username === null || data.username === 'me') { 92 | data.username = config.username; 93 | } 94 | 95 | return data; 96 | } 97 | 98 | function parse(argv) 99 | { 100 | var data = defaults.data(); 101 | 102 | parseMessage(argv, data); 103 | parseLabels(argv, data); 104 | parseUser(argv, data); 105 | parseData(argv, data); 106 | parseDataJson(argv, data); 107 | 108 | return data; 109 | } 110 | module.exports = parse; -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var home = require('home'); 6 | var optimist = require('optimist'); 7 | var gitlab = require('node-gitlab'); 8 | var actions = require('../lib/actions') || {}; 9 | var stringify = require('../lib/stringify'); 10 | var optionsParser = require('../lib/parser/optionsParser'); 11 | var dataParser = require('../lib/parser/dataParser'); 12 | var resourceParser = require('../lib/parser/resourceParser'); 13 | 14 | var argv = optimist.argv; 15 | 16 | var deps = {}; // dependencies passed around 17 | 18 | var usage = fs.readFileSync( __dirname + '/../usage.txt', { encoding: 'utf8' }); 19 | optimist.usage(usage); 20 | 21 | if (argv.env) { 22 | console.log('The --env option is not used anymore. Please use --config instead.'); 23 | process.exit(1); 24 | } 25 | 26 | // help wanted or missing arguments, exit 27 | if (argv['h'] || argv['help'] || argv._.length < 1) { 28 | console.log(usage); 29 | process.exit(1); 30 | } 31 | 32 | // load environment 33 | var configPath = (typeof argv.config === 'string') ? argv.config : '~/.config/gitlab-cli/default.js'; 34 | try { 35 | var configFullPath = home.resolve(configPath); 36 | } catch(error) { 37 | 38 | } 39 | 40 | if (!configFullPath || !fs.existsSync(configFullPath)) { 41 | console.log('Configuration file could not be found under: ' + configPath + '\nPlease create it from the example under config/default.sample.js'); 42 | process.exit(1); 43 | } 44 | var config = require(configFullPath); 45 | 46 | // aliases file might not exist 47 | try { 48 | var aliases = require('../config/aliases'); 49 | } 50 | catch (error) { 51 | var aliases = {}; 52 | } 53 | 54 | 55 | // get action 56 | var action = argv._[0] || ''; 57 | action = action.trim(); 58 | 59 | // missing action 60 | if (action === '') { 61 | console.error('no action specified'); 62 | process.exit(1); 63 | } 64 | 65 | // unknown action 66 | if (typeof actions[action] !== 'function') { 67 | console.error('unknown action'); 68 | process.exit(1); 69 | } 70 | 71 | // get data 72 | deps.data = dataParser(argv); 73 | 74 | // get resource 75 | deps.resource = resourceParser(argv, aliases, deps.data); 76 | 77 | // get options 78 | deps.options = optionsParser(argv); 79 | 80 | // accept self-signed or unauthorized certificates 81 | if (deps.options.acceptCerts) { 82 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 83 | } 84 | 85 | // resolve "me" in username 86 | if (deps.options.username === "me") { 87 | deps.options.username = config.username; 88 | } 89 | 90 | // copy labels from data to options 91 | deps.options.labels = deps.data.labels; 92 | 93 | // copy prefixed id from resource to data 94 | if (deps.resource.id && !deps.data.id) { 95 | deps.data[deps.resource.type.substr(0, deps.resource.type.length - 1) + '_id'] = deps.resource.id; 96 | } 97 | 98 | if (deps.options.debug) { 99 | console.log('[DEBUG] initalized gitlab-cli'); 100 | console.log(deps); 101 | } 102 | 103 | // create gitlab client 104 | var client = gitlab.create({ 105 | api: config.url + '/api/v3', 106 | privateToken: config.privateToken 107 | }); 108 | client.username = config.username; 109 | 110 | // init actions 111 | actions.init({ 112 | client: client, 113 | argv: argv, 114 | resource: deps.resource, 115 | data: deps.data, 116 | options: deps.options }, onInit); 117 | 118 | function inFilters(item) 119 | { 120 | var i, key, filter; 121 | 122 | if (deps.options.filters) { 123 | 124 | for(key in deps.options.filters) { 125 | if (item[key]) { 126 | if (item[key] == deps.options.filters[key]) { 127 | return true; 128 | } 129 | } 130 | } 131 | 132 | return false; 133 | } 134 | else { 135 | return true; 136 | } 137 | } 138 | 139 | function hasLabels(item) 140 | { 141 | var i, label; 142 | if (deps.options.labels && deps.options.labels.length > 0) { 143 | if (item.labels) { 144 | for(i = 0; i < deps.options.labels.length; i++) { 145 | label = deps.options.labels[i]; 146 | 147 | if (hasLabel(item, label)) { 148 | return true; 149 | } 150 | } 151 | return false; 152 | } 153 | else { 154 | return false; 155 | } 156 | } 157 | else { 158 | return true; 159 | } 160 | } 161 | 162 | function hasLabel(item, label) 163 | { 164 | for(var i = 0; i < item.labels.length; i++) { 165 | if (item.labels[i] === label) { 166 | return true; 167 | } 168 | } 169 | return false; 170 | } 171 | 172 | function onInit(error) { 173 | if (error) { 174 | console.error(error); 175 | } 176 | else { 177 | // everything seems to be ok, so let's rock! 178 | actions[action](function(error, data) { 179 | if (error) { 180 | if (typeof error === 'object') { 181 | console.error('error: ' + error.data.resBody.message || ''); 182 | } 183 | else { 184 | console.error('error: ' + error); 185 | } 186 | } 187 | else { 188 | if (data) { 189 | if (typeof data === 'object') { 190 | if (typeof data.length === 'number') { 191 | 192 | data = data.filter(function(item) { 193 | return (inFilters(item) && hasLabels(item)); 194 | }); 195 | 196 | if (deps.options.json) { 197 | console.log(JSON.stringify(data, null, 2)); 198 | } 199 | else { 200 | data.forEach(function(item, i) { 201 | 202 | if (stringify[deps.resource.type]) { 203 | console.log(stringify[deps.resource.type](item)); 204 | } 205 | else { 206 | console.log(JSON.stringify(item, null, 2)); 207 | } 208 | console.log("-------------------------------------"); 209 | }); 210 | } 211 | } 212 | else if (inFilters(data) && hasLabels(data)) { 213 | if (deps.options.json === false && stringify[deps.resource.type]) { 214 | console.log(stringify[deps.resource.type](data)); 215 | } 216 | else { 217 | console.log(JSON.stringify(data, null, 2)); 218 | } 219 | } 220 | } 221 | else { 222 | console.log(data); 223 | } 224 | } 225 | if (!deps.options.json) console.log('done'); 226 | } 227 | }); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /lib/actions.js: -------------------------------------------------------------------------------- 1 | var projects = []; 2 | var users = []; 3 | var fs = require('fs'); 4 | var defaults = require('./defaults'); 5 | 6 | // aliases file might not exist 7 | try { 8 | var aliases = require('../config/aliases'); 9 | } 10 | catch (error) { 11 | var aliases = {}; 12 | } 13 | 14 | // dependencies 15 | var client = null; 16 | var argv = []; 17 | var resource = defaults.resource(); 18 | var options = defaults.options(); 19 | var data = defaults.data(); 20 | 21 | function getUserByUsername(username) 22 | { 23 | var i, user; 24 | 25 | for(i = 0; i < users.length; i++) { 26 | user = users[i]; 27 | if (user.username === username) { 28 | return user; 29 | } 30 | } 31 | 32 | return null; 33 | } 34 | 35 | function getUserIdByUsername(username) 36 | { 37 | var user = getUserByUsername(username); 38 | if (user) { 39 | return user.id; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | 46 | function getProjectByResource(resource) 47 | { 48 | var i, project; 49 | 50 | for(i = 0; i < projects.length; i++) { 51 | project = projects[i]; 52 | if (project.path_with_namespace === resource.namespace + '/' + resource.project) { 53 | return project; 54 | } 55 | } 56 | 57 | return null; 58 | } 59 | 60 | function getProjectIdByResource(resource) 61 | { 62 | var project = getProjectByResource(resource); 63 | if (project) { 64 | return project.id; 65 | } 66 | 67 | return null; 68 | } 69 | 70 | function getProjectsByNamespace(namespace, projects) 71 | { 72 | var _projects = []; 73 | 74 | projects.forEach(function(project, i) { 75 | if (project.path_with_namespace.substr(0, namespace.length) === namespace) { 76 | _projects.push(project); 77 | } 78 | }); 79 | 80 | return _projects; 81 | } 82 | 83 | function getActionVerb(action) 84 | { 85 | var verb = action; 86 | var lastChar = action.substr(-1, 1); 87 | 88 | if (lastChar === 'e') { 89 | verb = action.substr(0, action.length - 1); 90 | } 91 | else if (action === 'get') { 92 | verb = action + 't'; 93 | } 94 | 95 | verb += 'ing'; 96 | 97 | return verb; 98 | } 99 | 100 | function log(action) 101 | { 102 | // do not log on json output 103 | if (options.json) { 104 | return; 105 | } 106 | 107 | if (resource.project) { 108 | console.log(getActionVerb(action) + ' ' + resource.type + ' in project "' + resource.project + '" ...'); 109 | } 110 | else { 111 | console.log(getActionVerb(action) + ' all ' + resource.type + ' ...'); 112 | } 113 | } 114 | 115 | function checkResource(cb) 116 | { 117 | if (typeof client[resource.type] !== 'object') { 118 | cb('resource type "' + resource.type + '" does not exist', null); 119 | return false; 120 | } 121 | else { 122 | return true; 123 | } 124 | } 125 | 126 | function resolveProjectId() 127 | { 128 | if (resource.project) { 129 | data.id = getProjectIdByResource(resource); 130 | } 131 | 132 | return data; 133 | } 134 | 135 | function checkResourceId(cb) 136 | { 137 | var check = (data[resource.type.substr(0, resource.type.length - 1) + '_id']) || resource.id.length > 0 ? true : false; 138 | 139 | if (!check) { 140 | cb('missing resource id', null); 141 | } 142 | return check; 143 | } 144 | 145 | function checkProjectId(cb) 146 | { 147 | if (data.id) { 148 | return true; 149 | } 150 | else { 151 | cb('missing project', null); 152 | return false; 153 | } 154 | } 155 | 156 | function init(deps, cb) 157 | { 158 | client = deps.client || client; 159 | argv = deps.argv || argv; 160 | resource = deps.resource || resource; 161 | data = deps.data || data; 162 | options = deps.options || options; 163 | 164 | client.users.list({}, function onUsersLoaded(error, _users) { 165 | if (error) { 166 | cb(error); 167 | } 168 | else { 169 | users = _users; 170 | loadProjects(); 171 | } 172 | }); 173 | 174 | function loadProjects() 175 | { 176 | client.projects.list({per_page: 100}, function onProjectsLoaded(error, _projects) { 177 | projects = _projects; 178 | 179 | cb(error); 180 | }); 181 | } 182 | } 183 | module.exports.init = init; 184 | 185 | 186 | function get(cb) 187 | { 188 | log('get'); 189 | 190 | resolveProjectId(); 191 | 192 | if (checkResourceId(cb)) { 193 | client[resource.type].get(data, cb); 194 | } 195 | } 196 | module.exports.get = get; 197 | 198 | 199 | function list(cb) 200 | { 201 | log('list'); 202 | 203 | resolveProjectId(); 204 | 205 | if (checkResource(cb)) { 206 | data.per_page = data.per_page || 100; 207 | 208 | client[resource.type].list(data, function(error, data) { 209 | if (data) { 210 | if (resource.type === 'projects' && resource.namespace) { 211 | data = getProjectsByNamespace(resource.namespace, data); 212 | } 213 | } 214 | 215 | cb(error, data); 216 | }); 217 | } 218 | } 219 | module.exports.list = list; 220 | 221 | function create(cb) 222 | { 223 | log('create', resource); 224 | 225 | resolveProjectId(); 226 | 227 | if (checkResource(cb)) { 228 | // projects have 'names' not 'titles' 229 | if (resource.type === 'projects' && data.title && !data.name) { 230 | data.name = data.title; 231 | } 232 | client[resource.type].create(data, cb); 233 | } 234 | } 235 | module.exports.create = create; 236 | 237 | 238 | function update(cb) 239 | { 240 | log('update'); 241 | 242 | resolveProjectId(); 243 | 244 | if (checkResourceId(cb) && checkResource(cb)) { 245 | client[resource.type].update(data, cb); 246 | } 247 | } 248 | module.exports.update = update; 249 | 250 | 251 | function remove(cb) 252 | { 253 | log('remove'); 254 | 255 | resolveProjectId(); 256 | 257 | if (checkResourceId(cb) && checkResource(cb)) { 258 | client[resource.type].remove(data, cb); 259 | } 260 | } 261 | module.exports.remove = remove; 262 | 263 | 264 | function open(cb) 265 | { 266 | log('open'); 267 | 268 | resolveProjectId(); 269 | 270 | if (!checkProjectId(cb)) { 271 | return; 272 | } 273 | 274 | data.assignee_id = getUserIdByUsername(options.username); 275 | 276 | if (resource.type === 'issues') { 277 | if (checkResourceId(function(){})) { 278 | data.state_event = 'reopen'; 279 | 280 | update(cb); 281 | } 282 | else { 283 | create(cb); 284 | } 285 | } 286 | else { 287 | cb('you can only open issues', null); 288 | } 289 | } 290 | module.exports.open = open; 291 | 292 | 293 | function close(cb) 294 | { 295 | log('close'); 296 | 297 | resolveProjectId(); 298 | 299 | if (!checkProjectId(cb)) { 300 | return; 301 | } 302 | 303 | data.assignee_id = getUserIdByUsername(options.username); 304 | 305 | if (resource.type === 'issues') { 306 | if (checkResourceId(cb)) { 307 | data.state_event = 'close'; 308 | 309 | update(cb); 310 | } 311 | else { 312 | cb('missing issue id', null); 313 | } 314 | } 315 | else { 316 | cb('you can only close issues', null); 317 | } 318 | } 319 | module.exports.close = close; 320 | 321 | function saveAliases() 322 | { 323 | var out = 'var aliases = ' + JSON.stringify(aliases, null, 2) + ';' + 324 | '\n' + 325 | 'module.exports = aliases;'; 326 | 327 | fs.writeFileSync( __dirname + '/../config/aliases.js', out, { ecncoding: 'utf8' }); 328 | } 329 | 330 | function alias(cb) 331 | { 332 | console.log('creating alias ...'); 333 | 334 | var path = argv._[1] || ''; 335 | var alias = argv._[2] || ''; 336 | 337 | path.trim(); 338 | alias.trim(); 339 | 340 | if (path !== '' && alias !== '') { 341 | aliases[alias] = path; 342 | 343 | saveAliases(); 344 | 345 | cb(null, null); 346 | } 347 | else { 348 | cb('resource path or alias missing or invalid', null); 349 | } 350 | } 351 | module.exports.alias = alias; 352 | 353 | 354 | function removeAlias(cb) 355 | { 356 | console.log('removing alias ...'); 357 | 358 | var alias = argv._[1]; 359 | alias.trim(); 360 | 361 | if (alias !== '' && aliases[alias]) { 362 | delete aliases[alias]; 363 | 364 | saveAliases(); 365 | cb(null, null); 366 | } 367 | else { 368 | cb('alias does not exist or is invalid', null); 369 | } 370 | } 371 | module.exports['rm-alias'] = removeAlias; 372 | 373 | 374 | function listAliases(cb) 375 | { 376 | var out = ''; 377 | for(var alias in aliases) { 378 | out += alias +':\t\t' + aliases[alias] + '\n'; 379 | } 380 | cb(null, out); 381 | } 382 | module.exports['list-aliases'] = listAliases; --------------------------------------------------------------------------------