Skip to content

Commit 22026f2

Browse files
committed
Refactor to be a slightly more user-friendly and functional CLI
1 parent 41e12b0 commit 22026f2

14 files changed

+337
-141
lines changed

bin/fec

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env node
2+
3+
const commands = require('../commands'),
4+
pkg = require('../package.json'),
5+
updateNotifier = require('update-notifier'),
6+
yargs = require('yargs');
7+
8+
updateNotifier({pkg}).notify();
9+
10+
yargs
11+
.usage('$0 <cmd> [args]')
12+
.env('FEC')
13+
.command(commands.init)
14+
.command(commands.list)
15+
.command(commands.convert)
16+
.recommendCommands()
17+
.demandCommand()
18+
.help()
19+
.argv;

bin/fecloader

-69
This file was deleted.

commands/convert.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const fec = require('../');
2+
3+
module.exports = {
4+
command: 'convert',
5+
describe:
6+
'Pipe in an .fec file to convert it to newline-delimited JSON or psql COPY commands.',
7+
builder(yargs) {
8+
return yargs
9+
.positional('filing_id', {
10+
describe: 'the filing ID of the filing'
11+
})
12+
.options({
13+
format: {
14+
type: 'string',
15+
default: 'ndjson',
16+
choices: ['ndjson', 'psql'],
17+
describe: 'choose output format'
18+
}
19+
});
20+
},
21+
handler(options) {
22+
fec[options.format](options);
23+
}
24+
};

commands/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
module.exports = {
3+
init: require('./init'),
4+
list: require('./list'),
5+
convert: require('./convert')
6+
};

commands/init.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const { init } = require('../');
2+
3+
module.exports = {
4+
command: 'init',
5+
describe: 'Create a SQL schema in a database; uses PGHOST, PGDATABASE, PGUSER, PGPASSWORD environment variables to authenticate.',
6+
builder: {},
7+
handler: init
8+
};

commands/list.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const { api, rss, format } = require('../');
2+
3+
module.exports = {
4+
command: 'list',
5+
describe: 'Show a list of the latest e-filings from the FEC API or RSS feed.',
6+
builder: {
7+
key: {
8+
type: 'string',
9+
alias: 'fecKey',
10+
describe:
11+
'data.gov API key authorizing access to the FEC API, can set using environment variable FEC_KEY'
12+
},
13+
rss: {
14+
type: 'boolean',
15+
default: false,
16+
describe: 'use the FEC RSS feed as the source'
17+
},
18+
committee: {
19+
type: 'string',
20+
describe: 'ID of a committee for which to show filings'
21+
},
22+
format: {
23+
type: 'string',
24+
default: 'table',
25+
choices: ['table','ndjson','json','csv','tsv'],
26+
describe: 'choose output format'
27+
},
28+
headers: {
29+
type: 'boolean',
30+
default: true,
31+
describe: 'show column headers'
32+
},
33+
columns: {
34+
type: 'string',
35+
describe: 'choose columns to show'
36+
},
37+
columnLength: {
38+
type: 'integer',
39+
default: 30,
40+
describe:
41+
'how many characters to show in each table cell'
42+
}
43+
},
44+
handler: async options => {
45+
if (!options.rss && !options.key) {
46+
console.error(
47+
'Error: key needed to use FEC API, ' +
48+
'specify using --key or the FEC_KEY environment variable, ' +
49+
'get one at https://api.data.gov/signup/' +
50+
'or use --rss instead'
51+
);
52+
return;
53+
}
54+
55+
const columns = options.columns
56+
? options.columns.split(',')
57+
: options.format == 'table'
58+
? [
59+
'fec_file_id',
60+
'form_type',
61+
'coverage_end_date',
62+
'committee_id',
63+
'committee_name'
64+
]
65+
: null;
66+
67+
try {
68+
let filings = await (options.rss ? rss : api)(options);
69+
70+
console.log(
71+
format(filings, options.format, columns, options.headers, options.columnLength)
72+
);
73+
} catch (err) {
74+
console.error(err);
75+
}
76+
}
77+
};

index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11

22
module.exports = {
33
api: require('./lib/api'),
4-
create: require('./lib/create'),
4+
format: require('./lib/format'),
55
increment: require('./lib/increment'),
6-
json: require('./lib/json'),
6+
init: require('./lib/init'),
7+
ndjson: require('./lib/ndjson'),
78
psql: require('./lib/psql'),
89
rss: require('./lib/rss')
910
};

lib/api.js

100755100644
+37-16
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,44 @@
1-
const fetch = require('cross-fetch');
1+
const axios = require('axios');
22

3-
module.exports = async (options) => {
4-
const apiUrl = `https://api.open.fec.gov/v1/efile/filings/?sort=-receipt_date&per_page=100&api_key=${
5-
options.fec_key ? options.fec_key : ''
6-
}&page=1&cache=${Math.round(Math.random() * 100)}`;
3+
module.exports = async options => {
4+
if (!options.key) {
5+
throw new Error('Error: key needed to use FEC API,' +
6+
'specify using options.key, ' +
7+
'or get one at https://api.data.gov/signup/');
8+
}
9+
10+
const fecAPI = axios.create({
11+
baseURL: 'https://api.open.fec.gov/v1/',
12+
params: {
13+
api_key: options.key
14+
}
15+
});
716

8-
try {
9-
const res = await fetch(apiUrl);
17+
let endpoint = options.committee ?
18+
`/committee/${options.committee}/filings/` :
19+
'efile/filings';
1020

11-
if (res.status >= 400) {
12-
throw new Error('Bad response from server');
21+
const res = await fecAPI.get(endpoint, {
22+
params: {
23+
sort: '-receipt_date',
24+
per_page: 100,
25+
page: 1
1326
}
27+
});
1428

15-
const data = await res.json();
29+
return res.data.results.map(filing => {
30+
filing = {
31+
...filing
32+
};
1633

17-
let filings = data.results.map(filing => filing.fec_url);
34+
if (filing.fec_file_id) {
35+
filing.fec_file_id = parseInt(
36+
filing.fec_file_id
37+
.replace('FEC-', '')
38+
.replace('SEN-','')
39+
);
40+
}
1841

19-
return filings;
20-
} catch (err) {
21-
console.error(err);
22-
}
23-
}
42+
return filing;
43+
});
44+
};

lib/format.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const table = require('text-table'),
2+
dsv = require('d3-dsv');
3+
4+
module.exports = (rows, format, columns, headers = true, maxLength = 30) => {
5+
if (!rows || rows.length === 0) {
6+
throw new Error('No results returned');
7+
}
8+
9+
if (columns) {
10+
rows = rows.map(row =>
11+
Object.entries(row)
12+
.filter(entry => columns.includes(entry[0]))
13+
.reduce(
14+
(obj, [key, val]) => Object.assign(obj, { [key]: val }),
15+
{}
16+
)
17+
);
18+
}
19+
20+
switch (format) {
21+
case 'json':
22+
return JSON.stringify({
23+
rows: rows
24+
});
25+
case 'ndjson':
26+
return rows.map(JSON.stringify).join('\n');
27+
case 'table':
28+
return table(
29+
(columns
30+
? (headers ? [columns] : []).concat(
31+
rows.map(row => columns.map(key => row[key]))
32+
)
33+
: rows
34+
).map(row =>
35+
row.map(val =>
36+
val && val.length > maxLength
37+
? `${val.substr(0, maxLength).trim()}…`
38+
: val || ''
39+
)
40+
)
41+
);
42+
case 'csv':
43+
case 'tsv':
44+
return dsv[format + (headers ? 'Format' : 'FormatBody')](rows, columns);
45+
default:
46+
throw new Error(
47+
'Invalid format specified, options are: table, ndjson, csv'
48+
);
49+
}
50+
};

lib/create.js lib/init.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ const models = require('@publici/fec-model')({
22
driver: 'postgres'
33
});
44

5-
module.exports = () => {
5+
module.exports = (options) => {
66
return models.sync();
77
}

lib/json.js lib/ndjson.js

File renamed without changes.

0 commit comments

Comments
 (0)