├── .gitignore
├── COPYING.text
├── README.markdown
├── package.json
└── pin-cushion
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/COPYING.text:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2015, Elliott Cable
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any Purpose with or
6 | without fee is hereby granted, provided that the above Copyright notice and this permission
7 | notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
10 | SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
11 | THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
12 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
13 | CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
14 | OR PERFORMANCE OF THIS SOFTWARE.
15 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | `pin-cushion` 









2 | =============
3 | A simple command-line [Pinboard.in][] client:
4 |
5 | pin-cushion [verb] [arguments]
6 | pin-cushion posts/recent
7 | pin-cushion posts/suggest --url "http://www.ponylang.org"
8 |
9 | pb-rename() {
10 | pin-cushion tags/rename --old "$1" --new "$2"
11 | }
12 |
13 | You get the idea. To use it, you must first record [your authentication token][auth] for the API:
14 |
15 | npm install -g pin-cushion
16 | pin-cushion --auth elliottcable:DEADBEEF1234567890
17 |
18 | This only provides abstracted access to the Pinboard API as defined on their site:
19 | > ###
20 |
21 | Any Pinboard API method described there may be passed as the `verb`; and all described arguments are
22 | accepted as command-line `flags`. These are not stored in this library; as your command-line
23 | instructions are simply converted directly to API calls; so this tool probably doesn't need much in
24 | the form of maintenance. `:P`
25 |
26 | [Pinboard.in]:
28 |
29 | ### Piping and JSON output
30 | If not explicitly passed a `--format` parameter, then `pin-cushion` will spit out a formatted
31 | object-description of the response, intended for human consumption. If a format is explicitly
32 | provided, then the response from the server will be printed, unmodified; this is particularly useful
33 | with the [`jq`](https://stedolan.github.io/jq/) command-line JSON manipulation tool:
34 |
35 | pin-cushion posts/recent --format=json | jq # Simply pretty-print
36 | pin-cushion posts/recent --format=json | jq '.posts[] | .href' # Extract URLs of recent pins
37 |
38 | This obviously lends itself to constructing complex shell pipes. Personally, I suggest aliasing
39 | this:
40 |
41 | pc() { pin-cushion "$1" --format=json "$@" ;}
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | { "author" : "ELLIOTTCABLE (http://ell.io/tt)"
2 |
3 | , "name" : "pin-cushion"
4 | , "version" : "1.0.0"
5 | , "license" : "ISC"
6 |
7 | , "description" : "Simple, maintained command-line interface to the Pinboard.in API."
8 | , "repository" : "ELLIOTTCABLE/pin-cushion"
9 |
10 | , "bin" : "pin-cushion"
11 |
12 | , "dependencies" : {
13 | "pinboard.js" : "^1.0.2"
14 | , "monowrap" : "^1.0.4"
15 | , "lodash" : "^4.0.0"
16 | , "minimist" : "^1.2.0"
17 | , "configstore" : "^1.4.0"
18 | , "debug" : "^2.2.0"
19 |
20 | , "js-yaml" : "^3.5.2"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pin-cushion:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var Pinboard = require('pinboard.js')
3 | , minimist = require('minimist')
4 | , lo = require('lodash')
5 | , YAML = require('js-yaml')
6 | , wrap = require('monowrap')
7 |
8 | , Configstore = require('configstore')
9 | , node_debug = require('debug')
10 | , package = require('./package.json')
11 |
12 | var config = new Configstore(package.name)
13 | , flags = require('minimist')(process.argv.slice(2), {
14 | '--' : true
15 | , 'alias' : { 'D': 'debug' }
16 | , 'boolean': [ 'debug' ]
17 | })
18 |
19 | var args = flags._.concat(flags['--'])
20 | , verb = lo.camelCase(args[0])
21 |
22 | , debugging = flags.debug
23 |
24 | , noArgs, API, dumpYAML
25 |
26 | if (debugging) {
27 | node_debug.enable('pin-cushion')
28 | delete flags.debug
29 | }
30 |
31 | debug = node_debug('pin-cushion')
32 | debug('-- verb: %j', verb)
33 | debug('-- args: %j', args)
34 | debug('-- flags: %j', flags)
35 |
36 | if (flags.auth != null) {
37 | if (!lo.isString(flags.auth) || !lo.includes(flags.auth, ':')) {
38 | console.error("Usage: %s --auth 'username:DEADBEEF'", require('path').basename(process.argv[1]))
39 | console.error(" (API token obtained at .)")
40 | process.exit(10)
41 | }
42 | var auth = flags.auth.split(':')
43 | config.set('username', auth[0])
44 | config.set('token', auth[1])
45 | console.error('-- Authentication information recorded!')
46 | process.exit(0)
47 | }
48 |
49 | function usage(API){
50 | console.error("Usage: %s [verb] [arguments]", require('path').basename(process.argv[1]))
51 | console.error(" %s --auth 'username:DEADBEEF'", require('path').basename(process.argv[1]))
52 |
53 | if (null != API) {
54 | var verbs = Object.keys(API.__proto__).map(function(key){
55 | return lo.kebabCase(key).split('-').join('/') })
56 |
57 | console.error(wrap(
58 | "Verbs: " + verbs.join(', ')
59 | , { top: 1, width: process.stdout.columns } ))
60 | }
61 |
62 | console.error(wrap(
63 | "API token from ; more usage examples at " +
64 | "; and full API documentation at " +
65 | "!"
66 | , { top: 1, width: process.stdout.columns } ))
67 | process.exit(10)
68 | }
69 |
70 | if (flags.format == null) {
71 | dumpYAML = true
72 | flags.format = 'json'
73 | }
74 |
75 |
76 | // --- ---- /!\ ---- ----
77 |
78 | try {
79 | API = new Pinboard({ auth: {
80 | type: 'token',
81 | username: config.get('username'),
82 | token: config.get('token') } })
83 | } catch (e) {
84 | if (e.message === "`params.auth.username` is required for authentication. Please provide it.") {
85 | if (args.length < 1) usage()
86 |
87 | console.error("-- Error: You must authenticate with `--auth` first!")
88 | process.exit(11)
89 | }
90 | }
91 |
92 | if (args.length < 1) usage(API)
93 |
94 | if (null == API[verb]) {
95 | console.error("-- Error: '%s' verb is unknown to `pinboard.js`!", verb)
96 | process.exit(12)
97 | }
98 |
99 | API[verb](flags, function callback(err, response, body){
100 | debug('Response!')
101 | debug('-- err: %j', err)
102 | debug('-- response: %j', response)
103 | debug('-- body: %s', body)
104 |
105 | if (err != null) {
106 | console.error('-- Error: %s', err)
107 | process.exit(9)
108 | }
109 |
110 | if (response.statusCode < 200 || response.statusCode > 299) {
111 | console.error('API error: %s', body)
112 | process.exit(Math.floor(response.statusCode / 100))
113 | }
114 |
115 | if (dumpYAML)
116 | body = YAML.safeDump(JSON.parse(body), {
117 | lineWidth: (process.stdout.columns || 80) - 2
118 | })
119 |
120 | console.log(body)
121 | })
122 |
--------------------------------------------------------------------------------