Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add shell.

  • Loading branch information...
commit c14d699bbc3907d69b351528ae3b1aa4a638e75c 1 parent 3b7b148
@chjj authored
Showing with 606 additions and 2 deletions.
  1. +54 −0 README.md
  2. +507 −0 bin/charged
  3. +40 −0 man/charged.1
  4. +5 −2 package.json
View
54 README.md
@@ -2,6 +2,8 @@
A high-level binding to the [Chargify](http://chargify.com/) API.
+Charged also provides an interactive chargify shell for managing sites.
+
# Example Usage
``` js
@@ -17,3 +19,55 @@ chargify.getSubscriptionsByCustomerRef('chjj', function(err, results) {
console.log(results);
});
```
+
+``` bash
+$ charged my-site
+API Key: foobar
+[charged] ls /stats
+{ seller_name: 'My Site Inc.',
+ site_name: 'my-site',
+ stats:
+ { revenue_this_year: '$34,578.01',
+ total_revenue: '$34,581.01',
+ total_subscriptions: 1648,
+ subscriptions_today: 0,
+ revenue_this_month: '$18.00',
+ revenue_today: '$18.00' } }
+[charged] ls /customers/foo
+{ customer:
+ { reference: 'foo',
+ email: 'foo@example.com',
+ country: null,
+ updated_at: '2012-09-27T15:01:17-04:00',
+ address: null,
+ created_at: '2012-08-26T16:53:03-04:00',
+ id: 2000,
+ state: null,
+ first_name: 'Foo',
+ last_name: 'Bar',
+ zip: null,
+ city: null,
+ organization: null,
+ phone: null,
+ address_2: null } }
+[charged] cat /product_families
+[ { product_family:
+ { name: 'my-family',
+ accounting_code: null,
+ id: 20000,
+ description: 'Main product family.',
+ handle: 'my-family' } } ]
+[charged] less /subscriptions
+[charged] mk /subscriptions {product_handle:'my-product',customer_reference:'foo'}
+[charged] mv {product_handle:'my-product2'} /subscriptions/[last-id]
+[charged] ls customers/foo/subscriptions
+[charged] cd customers
+[charged] pwd
+/customers
+[charged] rm foo
+# All of the Charged method names work as well...
+[charged] get-stats
+[charged] get-subscription 1000
+[charged] update-subscription 1000 {product_handle:'my-product'}
+[charged] get-customer-by-ref foo
+```
View
507 bin/charged
@@ -0,0 +1,507 @@
+#!/usr/bin/env node
+
+/**
+ * Charged Shell (https://github.com/chjj/charged)
+ * Copyright (c) 2012, Christopher Jeffrey (MIT License)
+ * Usage: $ charged my-site
+ */
+
+/**
+ * Modules
+ */
+
+var util = require('util')
+ , readline = require('readline')
+ , charged = require('../')
+ , resolve = require('path').resolve;
+
+var spawn = require('child_process').spawn
+ , fs = require('fs');
+
+/**
+ * Shell
+ */
+
+var shell = charged.shell = exports;
+
+/**
+ * Builtins
+ */
+
+shell.cwd = '/';
+
+shell.toplevel = [
+ '.',
+ 'coupons/',
+ 'customers/',
+ 'events/',
+ 'products/',
+ 'product_families/',
+ 'stats/',
+ 'statements/',
+ 'subscriptions/',
+ 'transactions/',
+ 'webhooks/'
+];
+
+shell.builtin = {
+ 'cd': function(path, callback) {
+ shell.cwd = resolve('/', shell.cwd, path);
+ return callback();
+ },
+ 'pwd': function(callback) {
+ return callback(null, shell.cwd);
+ },
+ 'ls': function(path, callback) {
+ if (!callback) {
+ callback = path;
+ path = null;
+ }
+
+ path = path || '';
+ path = resolve('/', shell.cwd, path);
+ path = path.replace(/^\.?\/+|\/+$/g, '');
+
+ if (!path && shell.cwd === '/') {
+ return callback(null, shell.toplevel.join('\n'));
+ }
+
+ var parts = path.split('/');
+ if (parts[0] === 'customers' && parts[1] && !/^\d+$/.test(parts[1])) {
+ var ref = parts.splice(1, 1)[0];
+ if (parts[1] === 'subscriptions') {
+ return this.getSubscriptionsByCustomerRef(ref, callback);
+ }
+ parts.push('lookup?reference=' + ref);
+ path = parts.join('/');
+ }
+
+ return this.get('/' + path, callback);
+ },
+ 'less': function(path, callback) {
+ return shell.proc(path, 'less', ['-R'], callback);
+ },
+ 'cat': function() {
+ return this.ls.apply(this, arguments);
+ },
+ 'rm': function(path, callback) {
+ if (!callback) {
+ callback = path;
+ path = null;
+ }
+
+ if (!path) {
+ return callback(new Error('Argument required.'));
+ }
+
+ path = resolve('/', shell.cwd, path);
+ path = path.replace(/^\.?\/+|\/+$/g, '');
+
+ var parts = path.split('/');
+ if (parts[0] === 'customers' && parts[1] && !/^\d+$/.test(parts[1])) {
+ var ref = parts.splice(1, 1)[0];
+ parts.push('lookup?reference=' + ref);
+ path = parts.join('/');
+ }
+
+ return this.delete('/' + path, callback);
+ },
+ 'mv': function(options, path, callback) {
+ if (!callback) {
+ callback = path;
+ path = null;
+ }
+
+ if (!path) {
+ return callback(new Error('Argument required.'));
+ }
+
+ path = resolve('/', shell.cwd, path);
+ path = path.replace(/^\.?\/+|\/+$/g, '');
+
+ var parts = path.split('/');
+ if (parts[0] === 'customers' && parts[1] && !/^\d+$/.test(parts[1])) {
+ var ref = parts.splice(1, 1)[0];
+ parts.push('lookup?reference=' + ref);
+ path = parts.join('/');
+ }
+
+ if (!options || !path || !callback) {
+ return callback(new Error('Argument required.'));
+ }
+
+ return this.put('/' + path, options, callback);
+ },
+ 'mk': function(path, options, callback) {
+ if (!callback) {
+ callback = options;
+ options = path;
+ path = null;
+ }
+
+ path = path || 'subscriptions';
+ path = resolve('/', shell.cwd, path);
+ path = path.replace(/^\.?\/+|\/+$/g, '');
+
+ if (!options || !path || !callback) {
+ return callback(new Error('Argument required.'));
+ }
+
+ return this.post('/' + path, options, callback);
+ },
+ 'set': function (key, value, callback) {
+ if (!key || !value || !callback) {
+ return callback(new Error('Argument required.'));
+ }
+ shell.chargify[toCamel(key)] = value;
+ return callback();
+ },
+ 'help': function (callback) {
+ shell.help();
+ return callback(null, shell.commands.join(' '));
+ }
+};
+
+shell.proc = function(path, name, args, callback) {
+ return shell.chargify.ls(path, function(err, results) {
+ if (err) results = err.message;
+
+ results = inspect(results);
+
+ var tmp = process.env.HOME + '/.charged-tmp~' + Date.now();
+ fs.writeFile(tmp, results, function(err) {
+ if (err) return callback(err);
+
+ var ps = spawn(name, args.concat(tmp), {
+ cwd: process.cwd(),
+ env: process.env,
+ setsid: false,
+ customFds: [0, 1, 2]
+ });
+
+ ps.on('exit', function() {
+ fs.unlink(tmp, function() {
+ return callback();
+ });
+ });
+ });
+ });
+};
+
+Object.keys(shell.builtin).forEach(function(name) {
+ if (typeof shell.builtin[name] === 'function') {
+ charged.prototype[name] = shell.builtin[name];
+ } else {
+ charged.prototype[name] = charged.prototype[shell.builtin[name]];
+ }
+});
+
+/**
+ * Commands
+ */
+
+shell.commands = Object.keys(charged.prototype).filter(function(name) {
+ return typeof charged.prototype[name] === 'function';
+}).map(toDash);
+
+/**
+ * Execute Command
+ */
+
+shell.exec = function(args, callback) {
+ var cmd = args.shift();
+
+ if (!cmd) {
+ return callback();
+ }
+
+ if (!~shell.commands.indexOf(cmd)) {
+ console.error('No such command: "' + cmd + '".');
+ return callback();
+ }
+
+ cmd = toCamel(cmd);
+
+ args.forEach(function(arg, i) {
+ if (!arg) return;
+
+ try {
+ if (arg[0] === '{' || arg[0] === '[') {
+ arg = 'return (' + arg + ');';
+ arg = new Function('', arg).call(null);
+ }
+ } catch (e) {
+ ;
+ }
+
+ args[i] = arg;
+ });
+
+ if (process.env.CHARGED_DEBUG) {
+ console.log(cmd, args);
+ return callback();
+ }
+
+ args.push(function(err, results) {
+ if (err) {
+ console.error(err.message);
+ return callback();
+ }
+ if (results) print(results);
+ return callback();
+ });
+
+ return shell.chargify[cmd].apply(shell.chargify, args);
+};
+
+/**
+ * Completions
+ */
+
+shell.complete = function(line, callback) {
+ var parts = line.replace(/^\s+/, '').split(/\s+/)
+ , out = [];
+
+ if (parts.length === 1) {
+ var i = shell.commands.length
+ , cmd = parts[0];
+
+ while (i--) {
+ if (shell.commands[i].indexOf(cmd) === 0) {
+ out.push(shell.commands[i]);
+ }
+ }
+
+ if (out.length === 1 && cmd === out[0]) {
+ // out[0] += ' ';
+ }
+ } else if (parts.length === 2) {
+ if (shell.builtin[parts[0]]) {
+ var i = shell.toplevel.length
+ , start = /^(\.?\/+|)/.exec(parts[1])[1]
+ , path = parts[1].substring(start.length);
+
+ if (shell.cwd !== '/' && start !== '/') return done();
+
+ while (i--) {
+ if (shell.toplevel[i].indexOf(path) === 0) {
+ out.push(parts[0] + ' ' + start + shell.toplevel[i]);
+ }
+ }
+
+ if (out.length === 1 && path === out[0]) {
+ // out[0] += ' ';
+ }
+ }
+ }
+
+ function done() {
+ return callback(null, [out, line]);
+ }
+
+ return done();
+};
+
+/**
+ * Help
+ */
+
+shell.help = function() {
+ console.error('Charged Shell');
+ console.error('Copyright (c) 2012, Christopher Jeffrey (MIT License)');
+ console.error('Usage: $ charged [subdomain] [api-key]\n'
+ + ' [--subdomain subdomain] [--key key]\n'
+ + ' [--site-key key] [--family family]');
+};
+
+/**
+ * Main
+ */
+
+shell.start = function() {
+ var options = parseArg();
+
+ shell.rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ completer: shell.complete
+ });
+
+ function askSubdomain(callback) {
+ if (options.subdomain) return callback();
+ shell.rl.question('Subdomain: ', function(result) {
+ options.subdomain = result.trim();
+ if (!options.subdomain) return askSubdomain(callback);
+ return callback();
+ });
+ }
+
+ function askApiKey(callback) {
+ if (options.apiKey) return callback();
+ shell.rl.question('API Key: ', function(result) {
+ options.apiKey = result.trim();
+ if (!options.apiKey) return askApiKey(callback);
+ return callback();
+ });
+ }
+
+ function askSiteKey(callback) {
+ if (options.siteKey) return callback();
+ shell.rl.question('Shared Site Key (optional): ', function(result) {
+ options.siteKey = result.trim();
+ return callback();
+ });
+ }
+
+ function askFamily(callback) {
+ if (options.defaultFamily) return callback();
+ shell.rl.question('Default Family (optional): ', function(result) {
+ options.defaultFamily = result.trim();
+ return callback();
+ });
+ }
+
+ function prompt() {
+ var wait;
+
+ if (!options.subdomain || !options.apiKey) {
+ return help();
+ }
+
+ shell.chargify = charged(options);
+
+ shell.rl.on('line', function(line) {
+ if (wait) return;
+
+ var args = line.trim().split(/\s+/);
+
+ wait = true;
+ shell.exec(args, function() {
+ wait = false;
+ shell.rl.prompt(true);
+ });
+ });
+
+ shell.rl.on('close', function() {
+ return process.exit(0);
+ });
+
+ shell.rl.setPrompt('\x1b[1;32;40m[charged]\x1b[m ', 10);
+
+ shell.rl.prompt(true);
+ }
+
+ return askSubdomain(function() {
+ return askApiKey(function() {
+ return prompt();
+ });
+ });
+};
+
+/**
+ * Helpers
+ */
+
+function toCamel(str) {
+ return str.replace(/-(\w)/g, function(_, ch) {
+ return ch.toUpperCase();
+ });
+}
+
+function toDash(str) {
+ return str.replace(/([a-z])([A-Z])/g, function(_, a, b) {
+ return a + '-' + b.toLowerCase();
+ });
+}
+
+function inspect(obj) {
+ if (typeof obj !== 'string') {
+ obj = util.inspect(obj, false, 4, true);
+ }
+ return obj;
+}
+
+function print(obj) {
+ return process.stdout.write(inspect(obj) + '\n');
+}
+
+/**
+ * Parse Arguments
+ */
+
+function parseArg() {
+ var argv = process.argv.slice(2)
+ , opt = {}
+ , arg;
+
+ function getarg() {
+ var arg = argv.shift();
+
+ if (arg.indexOf('--') === 0) {
+ // e.g. --opt
+ arg = arg.split('=');
+ if (arg.length > 1) {
+ // e.g. --opt=val
+ argv.unshift(arg.slice(1).join('='));
+ }
+ arg = arg[0];
+ } else if (arg[0] === '-') {
+ if (arg.length > 2) {
+ // e.g. -abc
+ argv = arg.substring(1).split('').map(function(ch) {
+ return '-' + ch;
+ }).concat(argv);
+ arg = argv.shift();
+ } else {
+ // e.g. -a
+ }
+ } else {
+ // e.g. foo
+ }
+
+ return arg;
+ }
+
+ while (argv.length) {
+ arg = getarg();
+ switch (arg) {
+ case '--family':
+ opt.defaultFamily = argv.shift();
+ break;
+ case '--site-key':
+ opt.siteKey = argv.shift();
+ break;
+ case '-k':
+ case '--key':
+ case '--api-key':
+ opt.apiKey = argv.shift();
+ break;
+ case '-s':
+ case '--subdomain':
+ case '--site':
+ opt.subdomain = argv.shift();
+ break;
+ case '-h':
+ case '--help':
+ help();
+ return process.exit(0);
+ default:
+ if (!opt.subdomain) {
+ opt.subdomain = arg;
+ } else if (!opt.apiKey) {
+ opt.apiKey = arg;
+ }
+ break;
+ }
+ }
+
+ return opt;
+}
+
+/**
+ * Start
+ */
+
+if (!module.parent) {
+ shell.start();
+}
View
40 man/charged.1
@@ -0,0 +1,40 @@
+.ds q \N'34'
+.TH charged 1
+.SH NAME
+charged \- a chargify shell and library
+.SH SYNOPSIS
+.nf
+.B charged [\-s subdomain] [\-k api-key] [\-\-site-key key] [\-\-family family]
+.fi
+.SH DESCRIPTION
+.B charged
+is a high-level binding to the Chargify API, as well as an interactive shell.
+.SH OPTIONS
+.TP
+.BI \-s,\ \-\-subdomain,\ \-\-site\ [subdomain]
+Specify the chargify site by subdomain. (Required).
+.TP
+.BI \-k,\ \-\-key,\ \-\-api\-key\ [key]
+The Chargify API key which corresponds to the specified site. (Required).
+.TP
+.BI \-\-site\-key\ [key]
+The Chargify shared site key for the specified site.
+.TP
+.BI \-\-family\ [family]
+The default product family ID for the specified site.
+.TP
+.BI \-h,\ \-\-help
+Display help information.
+.SH EXAMPLES
+.TP
+$ charged my-site
+.TP
+API Key: foobar
+.TP
+[charged] ls /stats
+.TP
+[charged] get-subscription 1000
+.SH BUGS
+Please report any bugs to https://github.com/chjj/charged.
+.SH LICENSE
+Copyright (c) 2012, Christopher Jeffrey (MIT License)
View
7 package.json
@@ -4,11 +4,14 @@
"author": "Christopher Jeffrey",
"version": "0.0.8",
"main": "./index.js",
+ "bin": "./bin/charged",
+ "man": "./man/charged.1",
+ "preferGlobal": true,
"repository": "git://github.com/chjj/charged.git",
"homepage": "https://github.com/chjj/charged",
"bugs": { "url": "https://github.com/chjj/charged/issues" },
- "keywords": [ "chargify", "charge" ],
- "tags": [ "chargify", "charge" ],
+ "keywords": ["chargify", "charge"],
+ "tags": ["chargify", "charge"],
"dependencies": {
"request": ">= 2.9.0"
}
Please sign in to comment.
Something went wrong with that request. Please try again.