Skip to content

Latest commit

 

History

History
260 lines (202 loc) · 7.59 KB

README.md

File metadata and controls

260 lines (202 loc) · 7.59 KB

Parsers

Parser is a function, which transforms a recording encoded in an arbitrary file format into a simple object representing a recording. Once the player fetches a file, it runs its contents through a parser, which turns it into a recording object used by player's recording driver.

Default parser used by the player is asciicast parser, however another built-in or custom parser can be used by including parser option in source argument of AsciinemaPlayer.create:

AsciinemaPlayer.create({ url: url, parser: parser }, containerElement);

Data model of a recording

asciinema player uses very simple internal representation of a recording. The object has following properties:

  • cols - number of terminal columns (terminal width in chars),
  • rows - number of terminal rows (terminal height in lines),
  • output - iterable (e.g. array, generator) of terminal writes, where each item is a 2 element array, containing write time (in seconds) + data written to a terminal,
  • input (optional) - iterable of terminal reads (individual key presses), where each item is a 2 element array, containing read time (in seconds) and a character that was read from keyboard.

Example recording in its internal representation:

{
  cols: 80,
  rows: 24,
  output: [
    [1.0, 'hello '],
    [2.0, 'world!']
  ]
}

Built-in parsers

Built-in parser can be used by passing the parser name (string) as parser option:

AsciinemaPlayer.create({ url: url, parser: 'built-in-parser-name' }, containerElement);

asciicast

asciicast parser handles both asciicast v1 and asciicast v2 file formats produced by asciinema recorder.

This parser is the default and does not have to be explicitly selected.

typescript

typescript parser handles recordings in typescript format (not to be confused with Typescript language) produced by venerable script command.

This parser supports both "classic" and "advanced" logging formats, including input streams.

Usage:

AsciinemaPlayer.create({
  url: ['/demo.timing', '/demo.data'],
  parser: 'typescript'
}, document.getElementById('demo'));

Note url above being an array of URLs pointing to typescript timing and data files.

Usage for 3 file variant - timing file + output file + input file (created when recording with script --log-in <file>):

AsciinemaPlayer.create({
  url: ['/demo.timing', '/demo.output', '/demo.input'],
  parser: 'typescript'
}, document.getElementById('demo'));

ttyrec

ttyrec parser handles recordings in ttyrec format produced by ttyrec, termrec or ipbt amongst others.

This parser understands \e[8;Y;Xt terminal size sequence injected into the first frame by termrec.

Usage:

AsciinemaPlayer.create({
  url: '/demo.ttyrec',
  parser: 'ttyrec'
}, document.getElementById('demo'));

Custom parser

Custom format parser can be used by using a function as parser option:

AsciinemaPlayer.create({ url: url, parser: myParserFunction }, containerElement);

Custom parser function takes a Response object and returns an object conforming to the recording data model.

The following example illustrates implementation of a custom parser:

import * as AsciinemaPlayer from 'asciinema-player';

function parse(response) {
  return {
    cols: 80,
    rows: 24,
    output: [[1.0, "hello"], [2.0, " world!"]]
  }
};

AsciinemaPlayer.create(
  { url: '/example.txt', parser: parse },
  document.getElementById('demo')
);

The above parse function returns a recording object, which makes the player print "hello" (at time = 1.0 sec), followed by "world!" a second later. The parser is then passed to create together with a URL as source argument, which makes the player fetch a file (example.txt) and pass it through the parser function.

This parser is not quite there though because it ignores downloaded file's content, always returning hardcoded output. Also, cols and rows are made up as well - if possible they should be extracted from the file and reflect the size of a terminal at the time recording session happened. The example illustrates what kind of data the player expects though.

A more realistic example, where content of a file is actually used to construct output, could look like this:

async function parseLogs(response) {
  const text = await response.text();

  return {
    cols: 80,
    rows: 24,
    output: text.split('\n').map((line, i) => [i * 0.5, line + '\n'])
  }
};

AsciinemaPlayer.create(
  { url: '/example.log', parser: parseLogs },
  document.getElementById('demo')
);

parseLogs function parses a log file into a recording which prints one log line every half a second.

This replays logs at a fixed rate. That's not very fun to watch. If log lines started with a timestamp (where 0.0 means start of the recording) followed by log message then the timestamp could be used for output timing.

For example:

# example.log
1.0 first log line
1.2 second log line
3.8 third log line
async function parseLogs(response) {
  const text = await response.text();
  const pattern = /^([\d.]+) (.*)/;

  return {
    cols: 80,
    rows: 24,
    output: text.split('\n').map(line => {
      const [_, time, message] = pattern.exec(line);
      return [parseFloat(time), message + '\n']
    })
  }
};

Here's slightly more advanced parser, for Simon Jansen's Star Wars Asciimation:

const LINES_PER_FRAME = 14;
const FRAME_DELAY = 67;
const COLUMNS = 67;

async function parseAsciimation(response) {
  const text = await response.text();
  const lines = text.split('\n');

  return {
    cols: COLUMNS,
    rows: LINES_PER_FRAME - 1,

    output: function*() {
      let time = 0;
      let prevFrameDuration = 0;

      yield [0, '\x9b?25l']; // hide cursor

      for (let i = 0; i + LINES_PER_FRAME - 1 < lines.length; i += LINES_PER_FRAME) {
        time += prevFrameDuration;
        prevFrameDuration = parseInt(lines[i], 10) * FRAME_DELAY;
        const frame = lines.slice(i + 1, i + LINES_PER_FRAME).join('\r\n');
        let text = '\x1b[H'; // move cursor home
        text += '\x1b[J'; // clear screen
        text += frame; // print current frame's lines
        yield [time / 1000, text];
      }
    }()
  }
}

AsciinemaPlayer.create(
  { url: '/starwars.txt', parser: parseAsciimation },
  document.getElementById('demo')
);

It parses Simon's Asciimation in its original format (please do not redistribute without giving Simon credit for it), where each animation frame is defined by 14 lines. First of every 14 lines defines duration a frame should be displayed for (multiplied by a speed constant, by default 67 ms), while lines 2-14 define frame content - text to display.

Note that output in the above parser function is a generator (note * in function*) that is immediately called (note () after } at the end). In fact output can be any iterable or iterator which is finite, which in practice means you can return an array or a finite generator, amongst others.