Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[limit_output] updates including allowing per-cell override of limit #813

Merged
merged 8 commits into from
Dec 3, 2016
94 changes: 64 additions & 30 deletions src/jupyter_contrib_nbextensions/nbextensions/limit_output/main.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
// Restrict output in a codecell to a maximum length

define([
'base/js/namespace',
'jquery',
'notebook/js/outputarea',
'base/js/dialog',
'notebook/js/codecell',
'services/config',
'base/js/utils'
], function(IPython, $, oa, dialog, cc, configmod, utils) {
], function(oa, cc, configmod, utils) {
"use strict";

var base_url = utils.get_body_data("baseUrl");
Expand All @@ -32,51 +29,88 @@ define([
}
};

function is_finite_number (n) {
n = parseFloat(n);
return !isNaN(n) && isFinite(n);
}

config.loaded.then(function() {
update_params();
var MAX_CHARACTERS = params.limit_output;
// sometimes limit_output metadata val can get stored as a string
params.limit_output = parseFloat(params.limit_output);

oa.OutputArea.prototype._handle_output = oa.OutputArea.prototype.handle_output;
var old_handle_output = oa.OutputArea.prototype.handle_output;
oa.OutputArea.prototype.handle_output = function (msg) {
if (msg.header.msg_type.match("stream|execute_result|display_data")) {
var count = 0;
var handled_msg_types = ['stream', 'execute_result', 'display_data'];
if (handled_msg_types.indexOf(msg.header.msg_type) < 0) {
return old_handle_output.apply(this, arguments);
}
else {
// get MAX_CHARACTERS from cell metadata if present, otherwise param
var MAX_CHARACTERS = params.limit_output;
var cell_metadata = this.element.closest('.cell').data('cell').metadata;
if (is_finite_number(cell_metadata.limit_output)) {
MAX_CHARACTERS = parseFloat(cell_metadata.limit_output);
}

// read the length of already-appended outputs from our data
var count = this.element.data('limit_output_count') || 0;
// update count with the length of this message
var old_count = count;
if (msg.header.msg_type === "stream") {
count = String(msg.content.text).length;
} else {
count = Math.max(
count += String(msg.content.text).length;
}
else {
count += Math.max(
(msg.content.data['text/plain'] === undefined) ? 0 : String(msg.content.data['text/plain']).length,
(msg.content.data['text/html'] === undefined) ? 0 : String(msg.content.data['text/html']).length )
(msg.content.data['text/html'] === undefined) ? 0 : String(msg.content.data['text/html']).length
);
}
if (count > MAX_CHARACTERS) {
console.log("limit_output: output", count, "exceeded", MAX_CHARACTERS, "characters. Further output muted.");
// save updated count
this.element.data('limit_output_count', count);

if (count <= MAX_CHARACTERS) {
return old_handle_output.apply(this, arguments);
}
// if here, we'd exceed MAX_CHARACTERS with addition of this message.
if (old_count <= MAX_CHARACTERS) {
// Apply truncated portion of this message
var to_add = MAX_CHARACTERS - old_count;
if (msg.header.msg_type === "stream") {
msg.content.text = msg.content.text.substr(0, MAX_CHARACTERS)
} else {
msg.content.text = msg.content.text.substr(0, to_add);
}
else {
if (msg.content.data['text/plain'] !== undefined) {
msg.content.data['text/plain'] = msg.content.data['text/plain'].substr(0, MAX_CHARACTERS);
msg.content.data['text/plain'] = msg.content.data['text/plain'].substr(0, to_add);
}
if (msg.content.data['text/html'] !== undefined) {
msg.content.data['text/html'] = msg.content.data['text/html'].substr(0, MAX_CHARACTERS);
msg.content.data['text/html'] = msg.content.data['text/html'].substr(0, to_add);
}
}
var limitmsg = {};
limitmsg.data = [];
old_handle_output.apply(this, arguments);

// display limit notification messages
console.log(
"limit_output: Maximum message size of", MAX_CHARACTERS,
"exceeded with", count, "characters. Further output muted."
);
// allow simple substitutions for output length for quick debugging
limitmsg.data['text/html'] = params.limit_output_message.replace("{limit_output_length}", MAX_CHARACTERS)
.replace("{output_length}", count);
this._handle_output(msg);
return this.append_display_data(limitmsg);
var limitmsg = params.limit_output_message.replace("{limit_output_length}", MAX_CHARACTERS)
.replace("{output_length}", count);
this.append_output({
"output_type": "display_data",
"metadata": {}, // included to avoid warning
"data": {"text/html": limitmsg},
});
}
}
return this._handle_output(msg);
};

cc.CodeCell.prototype._execute = cc.CodeCell.prototype.execute;
cc.CodeCell.prototype.execute = function() {
var old_clear_output = oa.OutputArea.prototype.clear_output;
oa.OutputArea.prototype.clear_output = function () {
// reset counter on execution.
this.output_area.count = 0;
this.output_area.drop = false;
return this._execute();
this.element.data('limit_output_count', 0);
return old_clear_output.apply(this, arguments);
};
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
Limit Output
============


Description
-----------
This extension limits the number of characters a codecell will output as text or HTML.
This also allows to interrupt endless loops of print commands.

This extension limits the number of characters a codecell will output as text
or HTML.
This also allows the interruption of endless loops of print commands.

[![Demo Video](http://img.youtube.com/vi/U26ujuPXf00/0.jpg)](https://youtu.be/U26ujuPXf00)

You can set the number of characters using the ConfigManager:

```Python
```python
from notebook.services.config import ConfigManager
cm = ConfigManager().update('notebook', {'limit_output': 1000})
```

or by using the [jupyter_nbextensions_configurator](https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator)

The limit can also be set for an individual cell, using the cell's
`cell.metadata.limit_output`.


Internals
---------

Three types of messages are intercepted: `stream`, `execute_result`, `display_data`

For `stream`- type messages, the text string length is limited to `limit_output` number of characters
For other message types, `text/plain` and `text/html` content length is counted` and if either
exceeds `limit_output` charaters, will be truncated.
Three types of messages are intercepted: `stream`, `execute_result`, and
`display_data`. For `stream`-type messages, the text string length is limited
to `limit_output` number of characters.
For other message types, `text/plain` and `text/html` content length is
counted, and if either exceeds `limit_output` characters will be truncated to
`limit_output` number of characters.

The `limit_output_message` can be formatted to display the `limit_output` length and the current `output_length`,
respectively using the replacement fields `{limit_output_length}` and `{output_length}`.
The `limit_output_message` parameter can be formatted to display the
`limit_output` length and the current `output_length`, using the respective
replacement fields `{limit_output_length}` and `{output_length}`.