intertext
Version:
Services for Recurrent Text-related Tasks
515 lines (475 loc) • 18.5 kB
JavaScript
(function() {
//###########################################################################################################
var $, $as_event, $as_row, $async, $cleanup, $dividers_bottom, $dividers_top, $drain, $set_widths_etc, $show, $watch, CND, DATOM, SP, _new_state, alert, as_row, as_text, assign, badge, boxes, cast, copy, debug, freeze, get_divider, help, inspect, isa, jr, keys_toplevel, last_of, lets, new_datom, rpr, rpr_settings, select, to_width, type_of, types, urge, validate, values_alignment, values_overflow, warn, width_of, wrap_datom;
CND = require('cnd');
badge = 'INTERTEXT/TBL';
// log = CND.get_logger 'plain', badge
// info = CND.get_logger 'info', badge
// whisper = CND.get_logger 'whisper', badge
alert = CND.get_logger('alert', badge);
debug = CND.get_logger('debug', badge);
warn = CND.get_logger('warn', badge);
help = CND.get_logger('help', badge);
urge = CND.get_logger('urge', badge);
// echo = CND.echo.bind CND
//...........................................................................................................
({assign, jr} = CND);
types = require('./types');
({isa, validate, cast, last_of, type_of} = types);
SP = require('steampipes');
({$, $async, $watch, $show, $drain} = SP.export());
({jr} = CND);
DATOM = new (require('datom')).Datom({
dirty: false
});
({new_datom, wrap_datom, lets, freeze, select} = DATOM.export());
({to_width, width_of} = require('to-width'));
({inspect} = require('util'));
//-----------------------------------------------------------------------------------------------------------
this.$tabulate = function(settings = {}) {
var S, pipeline;
S = _new_state(settings);
//.........................................................................................................
pipeline = [$as_event(S), $set_widths_etc(S), $dividers_top(S), $dividers_bottom(S), $as_row(S), $cleanup(S)];
//.........................................................................................................
return SP.pull(...pipeline);
};
//-----------------------------------------------------------------------------------------------------------
this.$show_table = function(settings) {
throw new Error("not implemented");
};
//-----------------------------------------------------------------------------------------------------------
_new_state = function(settings) {
var S, box_style, ref, ref1, ref10, ref11, ref12, ref13, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, type;
S = {};
/* TAINT use intertype */
// validate_keys "settings", "one or more out of", ( Object.keys settings ), keys_toplevel
//.........................................................................................................
if (settings == null) {
settings = {};
}
S.width = (ref = settings['width']) != null ? ref : null;
S.alignment = (ref1 = settings['alignment']) != null ? ref1 : 'left';
S.fit = (ref2 = settings['fit']) != null ? ref2 : null;
S.ellipsis = (ref3 = settings['ellipsis']) != null ? ref3 : '…';
S.pad = (ref4 = settings['pad']) != null ? ref4 : '';
S.overflow = (ref5 = settings['overflow']) != null ? ref5 : 'show';
S.alignment = (ref6 = settings['alignment']) != null ? ref6 : 'left';
S.multiline = (ref7 = settings['multiline']) != null ? ref7 : false;
S.format = (ref8 = settings['format']) != null ? ref8 : null;
//.........................................................................................................
S.widths = copy((ref9 = settings['widths']) != null ? ref9 : []);
S.alignments = (ref10 = settings['alignments']) != null ? ref10 : [];
S.headings = (ref11 = settings['headings']) != null ? ref11 : true;
S.keys = (ref12 = settings['keys']) != null ? ref12 : null;
S.box = copy((ref13 = settings['box']) != null ? ref13 : copy(boxes['plain']));
if (isa.float(S.pad)) {
//.........................................................................................................
S.pad = ' '.repeat(S.pad);
}
if (isa.text(S.box)) {
//.........................................................................................................
S.box = box_style = boxes[S.box];
}
if (S.box == null) {
throw new Error(`unknown box style ${rpr(box_style)}`);
}
//.........................................................................................................
S.box.left = S.box.vs + S.pad;
S.box.center = S.pad + S.box.vs + S.pad;
S.box.right = S.pad + S.box.vs;
S.box.left_width = width_of(S.box.left);
S.box.center_width = width_of(S.box.center);
S.box.right_width = width_of(S.box.right);
//.........................................................................................................
/* TAINT use intertype */
// validate_keys "alignment", "one of", [ S.alignment, ], values_alignment
// validate_keys "overflow", "one of", [ S.overflow, ], values_overflow
if ((settings.format != null) && (type = type_of(settings.format)) !== 'function') {
throw new Error(`expected function for format, got a ${type}`);
}
//.........................................................................................................
if (S.overflow !== 'show') {
throw new Error("setting 'overflow' not yet supported");
}
if (S.fit != null) {
throw new Error("setting 'fit' not yet supported");
}
//.........................................................................................................
/* TAINT use intertype */
/* TAINT check widths etc. are non-zero integers */
/* TAINT check values in headings, widths, keys (?) */
//.........................................................................................................
return S;
};
//-----------------------------------------------------------------------------------------------------------
/* TAINT use intertype */
keys_toplevel = ['alignment', 'alignments', 'box', 'default', 'ellipsis', 'fit', 'headings', 'keys', 'overflow', 'pad', 'width', 'widths'];
values_overflow = ['show', 'hide'];
values_alignment = ['left', 'right', 'center'];
//-----------------------------------------------------------------------------------------------------------
$set_widths_etc = function(S) {
var is_first;
is_first = true;
return $(function(d, send) {
var _, base, base1, data, i, idx, j, key, ref, ref1, terminal_width;
if (!is_first) {
return send(d);
}
if (!select(d, '^data')) {
return send(d);
}
is_first = false;
({data} = d);
//...................................................................................................
if (S.keys == null) {
if (isa.list(data)) {
S.keys = (function() {
var i, len, results;
results = [];
for (idx = i = 0, len = data.length; i < len; idx = ++i) {
_ = data[idx];
results.push(idx);
}
return results;
})();
} else if (isa.object(data)) {
S.keys = (function() {
var results;
results = [];
for (key in data) {
results.push(key);
}
return results;
})();
} else {
throw new Error(`^intertext/tabulate/set_widths_etc@1^ expected a list or an object, got a ${CND.type_of(data)}`);
}
}
if (S.headings === true) {
S.headings = S.keys;
}
//...................................................................................................
if (S.width == null) {
({
columns: terminal_width
} = (require('..')).get_terminal_size());
/* TAINT correction varies with border style */
S.width = Math.max(10, (Math.floor(terminal_width / S.keys.length)) - 4);
}
//...................................................................................................
if (S.widths != null) {
for (idx = i = 0, ref = S.keys.length; (0 <= ref ? i < ref : i > ref); idx = 0 <= ref ? ++i : --i) {
if ((base = S.widths)[idx] == null) {
base[idx] = S.width;
}
}
} else {
S.widths = (function() {
var j, len, ref1, results;
ref1 = S.keys;
results = [];
for (j = 0, len = ref1.length; j < len; j++) {
key = ref1[j];
results.push(S.width);
}
return results;
})();
}
//...................................................................................................
if (S.alignments != null) {
for (idx = j = 0, ref1 = S.keys.length; (0 <= ref1 ? j < ref1 : j > ref1); idx = 0 <= ref1 ? ++j : --j) {
if ((base1 = S.alignments)[idx] == null) {
base1[idx] = S.alignment;
}
}
} else {
S.alignments = (function() {
var k, len, ref2, results;
ref2 = S.keys;
results = [];
for (k = 0, len = ref2.length; k < len; k++) {
key = ref2[k];
results.push(S.alignment);
}
return results;
})();
}
//...................................................................................................
return send(d);
});
};
//-----------------------------------------------------------------------------------------------------------
as_row = (S, data, keys = null, is_header = false) => {
var R, align, ellipsis, i, idx, key, keys_and_idxs, len, text, value, width;
R = [];
if (keys != null) {
keys_and_idxs = (function() {
var i, len, results;
results = [];
for (idx = i = 0, len = keys.length; i < len; idx = ++i) {
key = keys[idx];
results.push([key, idx]);
}
return results;
})();
} else {
keys_and_idxs = (function() {
var i, ref, results;
results = [];
for (idx = i = 0, ref = data.length; (0 <= ref ? i < ref : i > ref); idx = 0 <= ref ? ++i : --i) {
results.push([idx, idx]);
}
return results;
})();
}
if (S.multiline !== false) {
throw new Error(`^intertype/tabulate/as_row@2^ setting multiline ${rpr(S.multiline)} not supported`);
}
for (i = 0, len = keys_and_idxs.length; i < len; i++) {
[key, idx] = keys_and_idxs[i];
value = data[key];
text = as_text(S, value);
width = S.widths[idx];
align = S.alignments[idx];
ellipsis = S.ellipsis;
text = to_width(text, width, {align, ellipsis});
if (S.format != null) {
text = S.format(text, {
value,
row: data,
is_header,
key,
idx
});
}
R.push(text);
}
//.......................................................................................................
return S.box.left + (R.join(S.box.center)) + S.box.right;
};
//-----------------------------------------------------------------------------------------------------------
$as_row = function(S) {
return $(function(d, send) {
var data, is_header, text;
if (!select(d, '^data')) {
return send(d);
}
({data} = d);
text = as_row(S, data, S.keys, false);
is_header = false;
return send(new_datom('^table', {text}));
return send(d);
});
};
//-----------------------------------------------------------------------------------------------------------
get_divider = function(S, position) {
var R, center, column, count, i, idx, last_idx, left, len, ref, right, width;
switch (position) {
case 'top':
left = S.box.lt;
center = S.box.ct;
right = S.box.rt;
break;
case 'heading':
left = S.box.lm;
center = S.box.cm;
right = S.box.rm;
break;
case 'mid':
left = S.box.lm;
center = S.box.cm;
right = S.box.rm;
break;
case 'bottom':
left = S.box.lb;
center = S.box.cb;
right = S.box.rb;
break;
default:
throw new Error(`unknown position ${rpr(position)}`);
}
//.........................................................................................................
last_idx = S.widths.length - 1;
R = [];
ref = S.widths;
//.........................................................................................................
/* TAINT simplified calculation; assumes single-width glyphs and symmetric padding etc. */
for (idx = i = 0, len = ref.length; i < len; idx = ++i) {
width = ref[idx];
column = [];
if (idx === 0) {
column.push(left);
count = (S.box.left_width - 1) + width + ((S.box.center_width - 1) / 2);
} else if (idx === last_idx) {
column.push(center);
count = ((S.box.center_width - 1) / 2) + width + (S.box.right_width - 1);
} else {
column.push(center);
count = ((S.box.center_width - 1) / 2) + width + ((S.box.center_width - 1) / 2);
}
column.push(S.box.hs.repeat(count));
if (idx === last_idx) {
column.push(right);
}
R.push(column.join(''));
}
//.........................................................................................................
return R.join('');
};
// #-----------------------------------------------------------------------------------------------------------
// $dividers = ( S ) ->
// # return D.new_stream pipeline: [ ( $dividers_top S ), ( $dividers_mid S ), ( $dividers_bottom S ), ]
// return D.new_stream pipeline: [ ( $dividers_top S ), ( $dividers_bottom S ), ]
//...........................................................................................................
$dividers_top = function(S) {
var is_first;
is_first = true;
return $(function(d, send) {
var ref;
if (!is_first) {
return send(d);
}
is_first = false;
send(new_datom('^table', {
text: get_divider(S, 'top')
}));
//.......................................................................................................
if ((ref = S.headings) !== null && ref !== false) {
send(new_datom('^table', {
text: as_row(S, S.headings, null, true)
}));
send(new_datom('^table', {
text: get_divider(S, 'heading')
}));
}
//.......................................................................................................
return send(d);
});
};
//-----------------------------------------------------------------------------------------------------------
$dividers_bottom = function(S) {
return SP.window({
width: 2,
fallback: null
}, $(function(de, send) {
var d, e;
[d, e] = de;
if (d == null) {
return null;
}
send(d);
if (e == null) {
send(new_datom('^table', {
text: get_divider(S, 'bottom')
}));
}
return null;
}));
};
//-----------------------------------------------------------------------------------------------------------
$cleanup = function(S) {
return SP.$filter(function(d) {
return select(d, '^table');
});
};
//-----------------------------------------------------------------------------------------------------------
boxes = {
plain: {
lt: '┌',
ct: '┬',
rt: '┐',
lm: '├',
cm: '┼',
rm: '┤',
lb: '└',
cb: '┴',
rb: '┘',
vs: '│',
hs: '─'
},
round: {
lt: '╭',
ct: '┬',
rt: '╮',
lm: '├',
cm: '┼',
rm: '┤',
lb: '╰',
cb: '┴',
rb: '╯',
vs: '│',
hs: '─'
}
};
//===========================================================================================================
// HELPERS
//-----------------------------------------------------------------------------------------------------------
$as_event = function(S) {
return $(function(data, send) {
return send(new_datom('^data', {data}));
});
};
//-----------------------------------------------------------------------------------------------------------
/* TAINT this is a temporary local copy of the method defined in the `main` submodule; in the future,
both functions should be unified. */
rpr = function(...P) {
var x;
return ((function() {
var i, len, results;
results = [];
for (i = 0, len = P.length; i < len; i++) {
x = P[i];
results.push(inspect(x, rpr_settings));
}
return results;
})()).join(' ');
};
rpr_settings = {
depth: 2e308,
maxArrayLength: 2e308,
breakLength: 2e308,
compact: true
};
//-----------------------------------------------------------------------------------------------------------
as_text = function(S, x) {
var type;
if (x === void 0) {
return '○';
}
if (x === null) {
return '●';
}
if (x === '') {
return "''";
}
type = type_of(x);
if (type === 'nan' || type === 'nan' || type === 'infinity' || type === 'object' || type === 'list' || type === 'number') {
return rpr(x);
}
if (type !== 'text') {
return rpr(x);
}
x = x.replace(/\n/g, '⏎');
x = x.replace(/[\x00-\x1a\x1c-\x1f]/g, function($0) {
return String.fromCodePoint(($0.codePointAt(0)) + 0x2400);
});
x = x.replace(/\x1b(?!\[)/g, '␛');
return x;
};
//-----------------------------------------------------------------------------------------------------------
copy = function(x) {
if (isa.list(x)) {
return Object.assign([], x);
}
if (isa.object(x)) {
return Object.assign({}, x);
}
return x;
};
//###########################################################################################################
if (module === require.main) {
(() => {})();
}
}).call(this);
//# sourceMappingURL=tabulate.js.map