markdowny/src/tools/table.ts
2018-08-11 17:32:06 -05:00

212 lines
5 KiB
TypeScript

import * as comma from "add-commas";
import * as _ from "lodash";
import * as markdownTable from "markdown-table";
import * as yargs from "yargs";
import * as scanner from "../scanner";
export var command = "table";
export var describe = "Create a summary table of requested fields";
export function builder(yargs: yargs.Arguments)
{
return yargs
.help("help")
.alias("f", "fields")
.array("fields")
.default("fields", ["_basename", "title"])
.alias("t", "titles")
.array("titles")
.default("table-start", "| ")
.default("table-end", " |")
.default("table-delimiter", " | ")
.boolean("table-rule")
.default("table-rule", true)
.boolean("table-header")
.default("table-header", true)
.default("list-delimiter", ", ")
.alias("o", "output")
.default("output", "-")
.default("prefix", "")
.demand(1);
}
export function handler(argv: any)
{
var files = argv._.splice(1);
var data = scanner.scanFiles(argv, files);
render(argv, data);
}
export function render(argv, data)
{
// Parse out the options and fields from the sources.
var columns = parse(argv, argv.titles, argv.fields);
// Create the header row.
var header: any[] = [];
for (var column1 of columns)
{
header.push(column1.header);
}
// Create the initial table with the header.
var table = [header];
// Loop through the results and get the fields we need to display.
var totals: any = ["Totals"];
for (var metadata of data)
{
// Add the row to the table.
var row: any[] = [];
table.push(row);
// Loop through our fields and retrieve each one.
for (var index = 0; index < columns.length; index++)
{
// Grab the value, even if nested.
var column = columns[index];
var value = _.get(metadata, column.ref);
// If we have a list, format it with spaces.
if (Array.isArray(value))
{
value = value.join(argv.listDelimiter);
}
// If we have totals, then add them.
if (column.total)
{
totals[index] = totals[index] ? parseInt(totals[index]) : 0;
totals[index] += parseInt(value);
}
// If we have commas requested, then add those.
if (column.comma)
{
value = comma(value);
}
// Add the final row to the table.
row.push(value);
}
}
// If we have totals, then add it at the bottom.
if (totals.length > 1)
{
for (var index2 = 0; index2 < columns.length && index2 < totals.length; index2++)
{
var column2 = columns[index2];
if (column2.comma)
{
totals[index2] = comma(totals[index2]);
}
}
table.push(totals);
}
// Format the results in a table.
var formattedTable = markdownTable(table, {
align: columns.map(c => c.align),
delimiter: argv.tableDelimiter,
start: argv.tableStart,
end: argv.tableEnd,
rule: argv.tableRule
});
// If we don't want the header row, strip off the first line.
if (!argv.tableHeader)
{
formattedTable = formattedTable.substring(formattedTable.indexOf("\n") + 1);
}
console.log(formattedTable);
}
class Column
{
ref: string;
header: string;
align: string = "l";
comma: boolean = false;
total: boolean = false;
public set(field, header)
{
this.ref = this.parseSpecifier(field);
this.header = this.parseSpecifier(header ? header : field);
}
private parseSpecifier(spec): string
{
// See if we have options.
var m = spec.match(/^(.*?):([lcr.st]+)?$/);
if (m)
{
// We have a match, so put the first part as the specifier.
spec = m[1];
for (var s of m[2])
{
switch (s)
{
case "c":
case "l":
case "r":
case ".":
this.align = s;
break;
case "s":
this.comma = true;
break;
case "t":
this.total = true;
break;
}
}
}
// Return the resulting specifier.
return spec;
}
}
function parse(argv, titles, fields): Column[]
{
var columns: Column[] = [];
if (!titles) titles = [];
for (var index = 0; index < fields.length; index++)
{
var column = new Column();
column.set(
fields[index],
titles.length >= index ? titles[index] : undefined);
columns.push(column);
}
return columns;
}
/* Notes
Comma-separated list of lists. I like spaces.
*/